From bda1ffa6ce502fc98c62dcab2b7090815da6ceb5 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Thu, 20 Mar 2014 20:52:23 -0400 Subject: [PATCH] Finished refactoring of DI container and service locator. --- framework/BaseYii.php | 59 +++++++++++++++++++++++++++++------------------------------ framework/di/Container.php | 14 +++++++------- framework/di/ContainerInterface.php | 35 ----------------------------------- framework/di/ContainerTrait.php | 269 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- framework/di/Instance.php | 80 ++++++++++++++++++++++++++++++++++++++++---------------------------------------- framework/di/ServiceLocator.php | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- framework/di/ServiceLocatorTrait.php | 182 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- tests/unit/framework/di/ContainerTest.php | 2 +- tests/unit/framework/di/InstanceTest.php | 1 - tests/unit/framework/di/ServiceLocatorTest.php | 83 ++++++++++------------------------------------------------------------------------- 10 files changed, 144 insertions(+), 652 deletions(-) delete mode 100644 framework/di/ContainerInterface.php delete mode 100644 framework/di/ContainerTrait.php delete mode 100644 framework/di/ServiceLocatorTrait.php diff --git a/framework/BaseYii.php b/framework/BaseYii.php index 54a35f8..c6fc682 100644 --- a/framework/BaseYii.php +++ b/framework/BaseYii.php @@ -292,45 +292,44 @@ class BaseYii /** * Creates a new object using the given configuration. * - * The following kinds of configuration are supported: - * - * - a string: representing the class name of the object to be created - * - a configuration array: the array must contain a `class` element which is treated as the object class, - * and the rest of the name-value pairs will be used to initialize the corresponding object properties - * - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`). - * The callable should return a new instance of the object being created. + * You may view this method as an enhanced version of the `new` operator. + * The method supports creating an object based on a class name, a configuration array or + * an anonymous function. * * Below are some usage examples: * - * ~~~ - * $object = \Yii::createObject('app\components\GoogleMap'); - * $object = \Yii::createObject([ - * 'class' => 'app\components\GoogleMap', - * 'apiKey' => 'xyz', - * ]); - * $object = \Yii::createObject([ - * return new \yii\base\Object; + * ```php + * // create an object using a class name + * $object = Yii::createObject('yii\db\Connection'); + * + * // create an object using a configuration array + * $object = Yii::createObject([ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', * ]); - * ~~~ * - * Note that the last usage is mainly useful to create an object based on some dynamic configuration - * specified as a property of a component. + * // create an object with two constructor parameters + * $object = \Yii::createObject('MyClass', [$param1, $param2]); + * ``` * - * This method can be used to create any object as long as the object's constructor is - * defined like the following: + * Using [[\yii\di\Container|dependency injection container]], this method can also identify + * dependent objects, instantiate them and inject them into the newly created object. * - * ~~~ - * public function __construct(..., $config = []) - * { - * } - * ~~~ + * @param string|array|callable $type the object type. This can be specified in one of the following forms: * - * The method will pass the given configuration as the last parameter of the constructor, - * and any additional parameters to this method will be passed as the rest of the constructor parameters. + * - a string: representing the class name of the object to be created + * - a configuration array: the array must contain a `class` element which is treated as the object class, + * and the rest of the name-value pairs will be used to initialize the corresponding object properties + * - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`). + * The callable should return a new instance of the object being created. * - * @param string|array|callable $config the configuration for creating the object. - * @return mixed the created object + * @param array $params the constructor parameters + * @return object the created object * @throws InvalidConfigException if the configuration is invalid. + * @see \yii\di\Container */ public static function createObject($type, array $params = []) { @@ -341,7 +340,7 @@ class BaseYii unset($type['class']); return static::$container->get($class, $params, $type); } elseif (is_callable($type, true)) { - return call_user_func($type, $params, static::$container); + return call_user_func($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); } else { diff --git a/framework/di/Container.php b/framework/di/Container.php index 0b97bbb..814d8ff 100644 --- a/framework/di/Container.php +++ b/framework/di/Container.php @@ -12,10 +12,11 @@ use yii\base\Component; use yii\base\InvalidConfigException; /** - * Container implements a dependency injection (DI) container. + * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container. * - * A DI container is an object that knows how to instantiate and configure objects and all their dependent objects. - * For more information about DI, please refer to [Martin Fowler's article](http://martinfowler.com/articles/injection.html). + * A dependency injection (DI) container is an object that knows how to instantiate and configure objects and + * all their dependent objects. For more information about DI, please refer to + * [Martin Fowler's article](http://martinfowler.com/articles/injection.html). * * Container supports constructor injection as well as property injection. * @@ -36,7 +37,6 @@ use yii\base\InvalidConfigException; * use yii\base\Object; * use yii\db\Connection; * use yii\di\Container; - * use yii\di\Instance; * * interface UserFinderInterface * { @@ -148,7 +148,7 @@ class Container extends Component if (is_callable($definition, true)) { $params = $this->resolveDependencies($this->mergeParams($class, $params)); - $object = call_user_func($definition, $params, $config, $this); + $object = call_user_func($definition, $this, $params, $config); } elseif (is_array($definition)) { $concrete = $definition['class']; unset($definition['class']); @@ -214,7 +214,7 @@ class Container extends Component * * // register a PHP callable * // The callable will be executed when $container->get('db') is called - * $container->set('db', function ($params, $config, $container) { + * $container->set('db', function ($container, $params, $config) { * return new \yii\db\Connection($config); * }); * ``` @@ -226,7 +226,7 @@ class Container extends Component * @param mixed $definition the definition associated with `$class`. It can be one of the followings: * * - a PHP callable: The callable will be executed when [[get()]] is invoked. The signature of the callable - * should be `function ($params, $config, $container)`, where `$params` stands for the list of constructor + * should be `function ($container, $params, $config)`, where `$params` stands for the list of constructor * parameters, `$config` the object configuration, and `$container` the container object. The return value * of the callable will be returned by [[get()]] as the object instance requested. * - a configuration array: the array contains name-value pairs that will be used to initialize the property diff --git a/framework/di/ContainerInterface.php b/framework/di/ContainerInterface.php deleted file mode 100644 index 630d0e6..0000000 --- a/framework/di/ContainerInterface.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\di; - -/** - * ContainerInterface specifies the interface that should be implemented by a dependency inversion (DI) container. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -interface ContainerInterface -{ - /** - * Returns a value indicating whether the container has the definition for the specified object type. - * @param string $type the object type. Depending on the implementation, this could be a class name, an interface name or an alias. - * @return boolean whether the container has the definition for the specified object. - */ - public function has($type); - - /** - * Returns an instance of the specified object type. - * - * If the container is unable to get an instance of the object type, an exception will be thrown. - * To avoid exception, you may use [[has()]] to check if the container has the definition for - * the specified object type. - * - * @param string $type the object type. Depending on the implementation, this could be a class name, an interface name or an alias. - */ - public function get($type); -} diff --git a/framework/di/ContainerTrait.php b/framework/di/ContainerTrait.php deleted file mode 100644 index 5c2bcce..0000000 --- a/framework/di/ContainerTrait.php +++ /dev/null @@ -1,269 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\di; - -use Yii; -use Closure; -use yii\base\InvalidConfigException; - -/** - * ContainerTrait implements the [[ContainerInterface]] that can turn a class into a service locator as well as a dependency injection container. - * - * By calling [[set()]] or [[setComponents()]], you can register with the container the components - * that may be later instantiated or accessed via [[get()]]. - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -trait ContainerTrait -{ - /** - * @var array shared component instances indexed by their IDs or types - */ - private $_components = []; - /** - * @var array component definitions indexed by their IDs or types - */ - private $_definitions = []; - - - /** - * Returns a value indicating whether the container has the specified component definition or has instantiated the shared component. - * This method may return different results depending on the value of `$checkInstance`. - * - * - If `$checkInstance` is false (default), the method will return a value indicating whether the container has the specified - * component definition. - * - If `$checkInstance` is true, the method will return a value indicating whether the container has - * instantiated the specified shared component. - * - * @param string $typeOrID component type (a fully qualified namespaced class/interface name, e.g. `yii\db\Connection`) or ID (e.g. `db`). - * When a class/interface name is given, make sure it does NOT have a leading backslash. - * @param boolean $checkInstance whether the method should check if the component is shared and instantiated. - * @return boolean whether the container has the component definition of the specified type or ID - * @see set() - */ - public function has($typeOrID, $checkInstance = false) - { - return $checkInstance ? isset($this->_components[$typeOrID]) : isset($this->_definitions[$typeOrID]); - } - - private $_building = []; - - /** - * Returns an instance of a component with the specified type or ID. - * - * If a component is registered as a shared component via [[set()]], this method will return - * the same component instance each time it is called. - * If a component is not shared, this method will create a new instance every time. - * - * @param string $typeOrID component type (a fully qualified namespaced class/interface name, e.g. `yii\db\Connection`) - * or ID (e.g. `db`). When a class/interface name is given, make sure it does NOT have a leading backslash. - * @param boolean $throwException whether to throw an exception if `$typeOrID` is not registered with the container before. - * @return object|null the component of the specified type or ID. If `$throwException` is false and `$typeOrID` - * is not registered before, null will be returned. - * @throws InvalidConfigException if `$typeOrID` refers to a nonexistent component ID - * or if there is cyclic dependency detected - * @see has() - * @see set() - */ - public function get($typeOrID, $throwException = true) - { - if (isset($this->_components[$typeOrID])) { - return $this->_components[$typeOrID]; - } - - if (!isset($this->_definitions[$typeOrID])) { - if (strpos($typeOrID, '\\') !== false) { - // a class name - return $this->buildComponent($typeOrID); - } elseif (!$throwException) { - return null; - } else { - throw new InvalidConfigException("Unknown component ID: $typeOrID"); - } - } - - if (isset($this->_building[$typeOrID])) { - throw new InvalidConfigException("A cyclic dependency of \"$typeOrID\" is detected."); - } - - $this->_building[$typeOrID] = true; - $definition = $this->_definitions[$typeOrID]; - if (is_string($definition)) { - // a type or ID - $component = $this->get($definition); - } elseif ($definition instanceof Closure || is_array($definition) && isset($definition[0], $definition[1])) { - // a PHP callable - $component = call_user_func($definition, $typeOrID, $this); - } elseif (is_object($definition)) { - // an object - $component = $definition; - } else { - // a configuration array - $component = $this->buildComponent($definition); - } - unset($this->_building[$typeOrID]); - - if (array_key_exists($typeOrID, $this->_components)) { - // a shared component - $this->_components[$typeOrID] = $component; - } - - return $component; - } - - /** - * Registers a component definition with this container. - * - * For example, - * - * ```php - * // a shared component identified by a class name. - * $container->set('yii\db\Connection', ['dsn' => '...']); - * - * // a non-shared component identified by a class name. - * $container->set('*yii\db\Connection', ['dsn' => '...']); - * - * // a shared component identified by an interface. - * $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); - * - * // a shared component identified by an ID. - * $container->set('db', ['class' => 'yii\db\Connection', 'dsn' => '...']); - * - * // a shared component defined by an anonymous function - * $container->set('db', function ($container) { - * return new \yii\db\Connection; - * }); - * ``` - * - * If a component definition with the same type/ID already exists, it will be overwritten. - * - * @param string $typeOrID component type or ID. This can be in one of the following three formats: - * - * - a fully qualified namespaced class/interface name: e.g. `yii\db\Connection`. - * This declares a shared component. Only a single instance of this class will be created and injected - * into different objects who depend on this class. If this is an interface name, the class name will - * be obtained from `$definition`. - * - a fully qualified namespaced class/interface name prefixed with an asterisk `*`: e.g. `*yii\db\Connection`. - * This declares a non-shared component. That is, if each time the container is injecting a dependency - * of this class, a new instance of this class will be created and used. If this is an interface name, - * the class name will be obtained from `$definition`. - * - an ID: e.g. `db`. This declares a shared component with an ID. The class name should - * be declared in `$definition`. When [[get()]] is called, the same component instance will be returned. - * - * Note that when a class/interface name is given, make sure it does NOT have a leading backslash. - * - * @param mixed $definition the component definition to be registered with this container. - * It can be one of the followings: - * - * - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`). - * The callable will be called by [[get()]] to return an object associated with the specified component type. - * The signature of the function should be: `function ($type, $container)`, where - * `$type` is the type or ID of the component to be created, and `$container` is this container. - * - an object: When [[get()]] is called, this object will be returned. No new object will be created. - * This essentially makes the component a shared one, regardless how it is specified in `$typeOrID`. - * - a configuration array: the array contains name-value pairs that will be used to initialize the property - * values of the newly created object when [[get()]] is called. The `class` element stands for the - * the class of the object to be created. If `class` is not specified, `$typeOrID` will be used as the class name. - * - a string: either a class name or a component ID that is registered with this container. - * - * If the parameter is null, the component definition will be removed from the container. - * @throws InvalidConfigException if the definition is an invalid configuration array - */ - public function set($typeOrID, $definition) - { - if ($notShared = $typeOrID[0] === '*') { - $typeOrID = substr($typeOrID, 1); - } - - if ($definition === null) { - unset($this->_components[$typeOrID], $this->_definitions[$typeOrID]); - return; - } - - if (is_object($definition) || is_array($definition) && isset($definition[0], $definition[1])) { - // an object or a PHP callable - $this->_definitions[$typeOrID] = $definition; - } elseif (is_array($definition)) { - // a configuration array - if (isset($definition['class'])) { - $this->_definitions[$typeOrID] = $definition; - } elseif (strpos($typeOrID, '\\')) { - $definition['class'] = $typeOrID; - $this->_definitions[$typeOrID] = $definition; - } else { - throw new InvalidConfigException("The configuration for the \"$typeOrID\" component must contain a \"class\" element."); - } - } else { - // a type or ID - $this->_definitions[$typeOrID] = $definition; - } - - if ($notShared) { - unset($this->_components[$typeOrID]); - } else { - $this->_components[$typeOrID] = null; - } - } - - /** - * Returns the list of the component definitions or the loaded shared component instances. - * @param boolean $returnDefinitions whether to return component definitions or the loaded shared component instances. - * @return array the list of the component definitions or the loaded shared component instances (type or ID => definition or instance). - */ - public function getComponents($returnDefinitions = true) - { - return $returnDefinitions ? $this->_definitions : $this->_components; - } - - /** - * Registers a set of component definitions in this container. - * - * This is the bulk version of [[set()]]. The parameter should be an array - * whose keys are component types or IDs and values the corresponding component definitions. - * - * For more details on how to specify component types/IDs and definitions, please - * refer to [[set()]]. - * - * If a component definition with the same type/ID already exists, it will be overwritten. - * - * The following is an example for registering two component definitions: - * - * ~~~ - * [ - * 'db' => [ - * 'class' => 'yii\db\Connection', - * 'dsn' => 'sqlite:path/to/file.db', - * ], - * 'cache' => [ - * 'class' => 'yii\caching\DbCache', - * 'db' => 'db', - * ], - * ] - * ~~~ - * - * @param array $components component definitions or instances - */ - public function setComponents($components) - { - foreach ($components as $typeOrID => $component) { - $this->set($typeOrID, $component); - } - } - - /** - * Builds a new component instance based on the given class name or configuration array. - * This method is mainly called by [[get()]]. - * @param string|array $type a class name or configuration array - * @return object the new component instance - */ - protected function buildComponent($type) - { - return Yii::createObject($type); - } -} diff --git a/framework/di/Instance.php b/framework/di/Instance.php index 81ef116..19ce6f6 100644 --- a/framework/di/Instance.php +++ b/framework/di/Instance.php @@ -11,39 +11,37 @@ use Yii; use yii\base\InvalidConfigException; /** - * Instance is a reference to a named component in a container. + * Instance represents a reference to a named object in a dependency injection (DI) container or a service locator. * - * You may use [[get()]] to obtain the actual component. + * You may use [[get()]] to obtain the actual object referenced by [[id]]. * * Instance is mainly used in two places: * - * - When configuring a dependency injection container, you use Instance to reference a component - * - In classes which use external dependent objects. + * - When configuring a dependency injection container, you use Instance to reference a class name, interface name + * or alias name. The reference can later be resolved into the actual object by the container. + * - In classes which use service locator to obtain dependent objects. * - * For example, the following configuration specifies that the "db" property should be - * a component referenced by the "db" component: + * The following example shows how to configure a DI container with Instance: * * ```php - * [ - * 'class' => 'app\components\UserFinder', - * 'db' => Instance::of('db'), - * ] + * $container = new \yii\di\Container; + * $container->set('cache', 'yii\caching\DbCache', Instance::of('db')); + * $container->set('db', [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'sqlite:path/to/file.db', + * ]); * ``` * - * And in `UserFinder`, you may use `Instance` to make sure the "db" property is properly configured: + * And the following example shows how a class retrieves a component from a service locator: * * ```php - * namespace app\components; - * - * use yii\base\Object; - * use yii\di\Instance; - * - * class UserFinder extends \yii\db\Object + * class DbCache extends Cache * { - * public $db; + * public $db = 'db'; * * public function init() * { + * parent::init(); * $this->db = Instance::ensure($this->db, 'yii\db\Connection'); * } * } @@ -55,7 +53,7 @@ use yii\base\InvalidConfigException; class Instance { /** - * @var string the component ID + * @var string the component ID, class name, interface name or alias name */ public $id; @@ -79,11 +77,12 @@ class Instance } /** - * Ensures that `$value` is an object or a reference to the object of the specified type. + * Resolves the specified reference into the actual object and makes sure it is of the specified type. * - * An exception will be thrown if the type is not matched. + * The reference may be specified as a string or an Instance object. If the former, + * it will be treated as a component ID, a class/interface name or an alias, depending on the container type. * - * Upon success, the method will return the object itself or the object referenced by `$value`. + * If you do not specify a container, the method will first try `Yii::$app` followed by `Yii::$container`. * * For example, * @@ -97,45 +96,46 @@ class Instance * $db = Instance::ensure($instance, Connection::className()); * ``` * - * @param object|string|static $value an object or a reference to the desired object. + * @param object|string|static $reference an object or a reference to the desired object. * You may specify a reference in terms of a component ID or an Instance object. - * @param string $type the class name to be checked - * @param ContainerInterface $container the container. If null, the application instance will be used. - * @return object - * @throws \yii\base\InvalidConfigException + * @param string $type the class/interface name to be checked. If null, type check will not be performed. + * @param ServiceLocator|Container $container the container. This will be passed to [[get()]]. + * @return object the object referenced by the Instance, or `$reference` itself if it is an object. + * @throws InvalidConfigException if the reference is invalid */ - public static function ensure($value, $type = null, $container = null) + public static function ensure($reference, $type = null, $container = null) { - if ($value instanceof $type) { - return $value; - } elseif (empty($value)) { + if ($reference instanceof $type) { + return $reference; + } elseif (empty($reference)) { throw new InvalidConfigException('The required component is not specified.'); } - if (is_string($value)) { - $value = new static($value); + if (is_string($reference)) { + $reference = new static($reference); } - if ($value instanceof self) { - $component = $value->get($container); + if ($reference instanceof self) { + $component = $reference->get($container); if ($component instanceof $type || $type === null) { return $component; } else { - throw new InvalidConfigException('"' . $value->id . '" refers to a ' . get_class($component) . " component. $type is expected."); + throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected."); } } - $valueType = is_object($value) ? get_class($value) : gettype($value); + $valueType = is_object($reference) ? get_class($reference) : gettype($reference); throw new InvalidConfigException("Invalid data type: $valueType. $type is expected."); } /** - * Returns the actual component referenced by this Instance object. - * @return object the actual component referenced by this Instance object. + * Returns the actual object referenced by this Instance object. + * @param ServiceLocator|Container $container the container used to locate the referenced object. + * If null, the method will first try `Yii::$app` then `Yii::$container`. + * @return object the actual object referenced by this Instance object. */ public function get($container = null) { - /** @var ContainerInterface $container */ if ($container) { return $container->get($this->id); } diff --git a/framework/di/ServiceLocator.php b/framework/di/ServiceLocator.php index 2690cc8..3e8530c 100644 --- a/framework/di/ServiceLocator.php +++ b/framework/di/ServiceLocator.php @@ -13,6 +13,33 @@ use yii\base\Component; use yii\base\InvalidConfigException; /** + * ServiceLocator implements a [service locator](http://en.wikipedia.org/wiki/Service_locator_pattern). + * + * To use ServiceLocator, you first need to register component IDs with the corresponding component + * definitions with the locator by calling [[set()]] or [[setComponents()]]. + * You can then call [[get()]] to retrieve a component with the specified ID. The locator will automatically + * instantiate and configure the component according to the definition. + * + * For example, + * + * ```php + * $locator = new \yii\di\ServiceLocator; + * $locator->setComponents([ + * 'db' => [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'sqlite:path/to/file.db', + * ], + * 'cache' => [ + * 'class' => 'yii\caching\DbCache', + * 'db' => 'db', + * ], + * ]); + * + * $db = $locator->get('db'); + * $cache = $locator->get('cache'); + * ``` + * + * Because [[\yii\base\Module]] extends from ServiceLocator, modules and the application are all service locators. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 @@ -85,33 +112,40 @@ class ServiceLocator extends Component * For example, * * ```php - * // via configuration array + * // a class name + * $locator->set('cache', 'yii\caching\FileCache'); + * + * // a configuration array * $locator->set('db', [ * 'class' => 'yii\db\Connection', - * 'dsn' => '...', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', * ]); * - * // via anonymous function - * $locator->set('db', function ($locator) { - * return new \yii\db\Connection; + * // an anonymous function + * $locator->set('cache', function ($params) { + * return new \yii\caching\FileCache; * }); + * + * // an instance + * $locator->set('cache', new \yii\caching\FileCache); * ``` * * If a component definition with the same ID already exists, it will be overwritten. * - * If `$definition` is null, the previously registered component definition will be removed. - * * @param string $id component ID (e.g. `db`). * @param mixed $definition the component definition to be registered with this locator. * It can be one of the followings: * + * - a class name + * - a configuration array: the array contains name-value pairs that will be used to + * initialize the property values of the newly created object when [[get()]] is called. + * The `class` element is required and stands for the the class of the object to be created. * - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`). * The callable will be called by [[get()]] to return an object associated with the specified component ID. - * The signature of the function should be: `function ($locator)`, where `$locator` is this locator. * - an object: When [[get()]] is called, this object will be returned. - * - a configuration array or a class name: the array contains name-value pairs that will be used to - * initialize the property values of the newly created object when [[get()]] is called. - * The `class` element stands for the the class of the object to be created. * * @throws InvalidConfigException if the definition is an invalid configuration array */ @@ -138,8 +172,17 @@ class ServiceLocator extends Component } /** + * Removes the component from the locator. + * @param string $id the component ID + */ + public function clear($id) + { + unset($this->_definitions[$id], $this->_components[$id]); + } + + /** * Returns the list of the component definitions or the loaded component instances. - * @param boolean $returnDefinitions whether to return component definitions or the loaded component instances. + * @param boolean $returnDefinitions whether to return component definitions instead of the loaded component instances. * @return array the list of the component definitions or the loaded component instances (ID => definition or instance). */ public function getComponents($returnDefinitions = true) @@ -159,7 +202,7 @@ class ServiceLocator extends Component * * The following is an example for registering two component definitions: * - * ~~~ + * ```php * [ * 'db' => [ * 'class' => 'yii\db\Connection', @@ -170,7 +213,7 @@ class ServiceLocator extends Component * 'db' => 'db', * ], * ] - * ~~~ + * ``` * * @param array $components component definitions or instances */ diff --git a/framework/di/ServiceLocatorTrait.php b/framework/di/ServiceLocatorTrait.php deleted file mode 100644 index b5e6dc5..0000000 --- a/framework/di/ServiceLocatorTrait.php +++ /dev/null @@ -1,182 +0,0 @@ -<?php -/** - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\di; - -use Yii; -use Closure; -use yii\base\InvalidConfigException; - -/** - * - * @author Qiang Xue <qiang.xue@gmail.com> - * @since 2.0 - */ -trait ServiceLocatorTrait -{ - /** - * @var array shared component instances indexed by their IDs - */ - private $_components = []; - /** - * @var array component definitions indexed by their IDs - */ - private $_definitions = []; - - - /** - * Returns a value indicating whether the locator has the specified component definition or has instantiated the component. - * This method may return different results depending on the value of `$checkInstance`. - * - * - If `$checkInstance` is false (default), the method will return a value indicating whether the locator has the specified - * component definition. - * - If `$checkInstance` is true, the method will return a value indicating whether the locator has - * instantiated the specified component. - * - * @param string $id component ID (e.g. `db`). - * @param boolean $checkInstance whether the method should check if the component is shared and instantiated. - * @return boolean whether the locator has the specified component definition or has instantiated the component. - * @see set() - */ - public function has($id, $checkInstance = false) - { - return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]); - } - - /** - * Returns the component instance with the specified ID. - * - * @param string $id component ID (e.g. `db`). - * @param boolean $throwException whether to throw an exception if `$id` is not registered with the locator before. - * @return object|null the component of the specified ID. If `$throwException` is false and `$id` - * is not registered before, null will be returned. - * @throws InvalidConfigException if `$id` refers to a nonexistent component ID - * @see has() - * @see set() - */ - public function get($id, $throwException = true) - { - if (isset($this->_components[$id])) { - return $this->_components[$id]; - } - - if (isset($this->_definitions[$id])) { - $definition = $this->_definitions[$id]; - if (is_object($definition) && !$definition instanceof Closure) { - return $this->_components[$id] = $definition; - } else { - return $this->_components[$id] = Yii::createObject($definition); - } - } elseif ($throwException) { - throw new InvalidConfigException("Unknown component ID: $id"); - } else { - return null; - } - } - - /** - * Registers a component definition with this locator. - * - * For example, - * - * ```php - * // via configuration array - * $locator->set('db', [ - * 'class' => 'yii\db\Connection', - * 'dsn' => '...', - * ]); - * - * // via anonymous function - * $locator->set('db', function ($locator) { - * return new \yii\db\Connection; - * }); - * ``` - * - * If a component definition with the same ID already exists, it will be overwritten. - * - * If `$definition` is null, the previously registered component definition will be removed. - * - * @param string $id component ID (e.g. `db`). - * @param mixed $definition the component definition to be registered with this locator. - * It can be one of the followings: - * - * - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`). - * The callable will be called by [[get()]] to return an object associated with the specified component ID. - * The signature of the function should be: `function ($locator)`, where `$locator` is this locator. - * - an object: When [[get()]] is called, this object will be returned. - * - a configuration array or a class name: the array contains name-value pairs that will be used to - * initialize the property values of the newly created object when [[get()]] is called. - * The `class` element stands for the the class of the object to be created. - * - * @throws InvalidConfigException if the definition is an invalid configuration array - */ - public function set($id, $definition) - { - if ($definition === null) { - unset($this->_components[$id], $this->_definitions[$id]); - return; - } - - if (is_object($definition) || is_callable($definition, true)) { - // an object, a class name, or a PHP callable - $this->_definitions[$id] = $definition; - } elseif (is_array($definition)) { - // a configuration array - if (isset($definition['class'])) { - $this->_definitions[$id] = $definition; - } else { - throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element."); - } - } else { - throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); - } - } - - /** - * Returns the list of the component definitions or the loaded component instances. - * @param boolean $returnDefinitions whether to return component definitions or the loaded component instances. - * @return array the list of the component definitions or the loaded component instances (ID => definition or instance). - */ - public function getComponents($returnDefinitions = true) - { - return $returnDefinitions ? $this->_definitions : $this->_components; - } - - /** - * Registers a set of component definitions in this locator. - * - * This is the bulk version of [[set()]]. The parameter should be an array - * whose keys are component IDs and values the corresponding component definitions. - * - * For more details on how to specify component IDs and definitions, please refer to [[set()]]. - * - * If a component definition with the same ID already exists, it will be overwritten. - * - * The following is an example for registering two component definitions: - * - * ~~~ - * [ - * 'db' => [ - * 'class' => 'yii\db\Connection', - * 'dsn' => 'sqlite:path/to/file.db', - * ], - * 'cache' => [ - * 'class' => 'yii\caching\DbCache', - * 'db' => 'db', - * ], - * ] - * ~~~ - * - * @param array $components component definitions or instances - */ - public function setComponents($components) - { - foreach ($components as $id => $component) { - $this->set($id, $component); - } - } -} diff --git a/tests/unit/framework/di/ContainerTest.php b/tests/unit/framework/di/ContainerTest.php index 71c8a11..ab2447d 100644 --- a/tests/unit/framework/di/ContainerTest.php +++ b/tests/unit/framework/di/ContainerTest.php @@ -63,7 +63,7 @@ class ContainerTest extends TestCase // wiring by closure which uses container $container = new Container; $container->set($QuxInterface, $Qux); - $container->set('foo', function ($params, $config, Container $c) { + $container->set('foo', function (Container $c, $params, $config) { return $c->get(Foo::className()); }); $foo = $container->get('foo'); diff --git a/tests/unit/framework/di/InstanceTest.php b/tests/unit/framework/di/InstanceTest.php index 316c244..e443617 100644 --- a/tests/unit/framework/di/InstanceTest.php +++ b/tests/unit/framework/di/InstanceTest.php @@ -8,7 +8,6 @@ namespace yiiunit\framework\di; use yii\base\Component; -use yii\base\Object; use yii\di\Container; use yii\di\Instance; use yiiunit\TestCase; diff --git a/tests/unit/framework/di/ServiceLocatorTest.php b/tests/unit/framework/di/ServiceLocatorTest.php index cce11fa..66139bd 100644 --- a/tests/unit/framework/di/ServiceLocatorTest.php +++ b/tests/unit/framework/di/ServiceLocatorTest.php @@ -8,14 +8,14 @@ namespace yiiunit\framework\di; use yii\base\Object; -use yii\di\Container; +use yii\di\ServiceLocator; use yiiunit\TestCase; class Creator { - public static function create($type, $container) + public static function create() { - return new $type; + return new TestClass; } } @@ -25,34 +25,19 @@ class TestClass extends Object public $prop2; } - /** * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */ class ServiceLocatorTest extends TestCase { - public function testDefault() - { - // without configuring anything - $container = new Container; - $className = TestClass::className(); - $object = $container->get($className); - $this->assertEquals(1, $object->prop1); - $this->assertTrue($object instanceof $className); - // check non-shared - $object2 = $container->get($className); - $this->assertTrue($object2 instanceof $className); - $this->assertTrue($object !== $object2); - } - public function testCallable() { // anonymous function - $container = new Container; + $container = new ServiceLocator; $className = TestClass::className(); - $container->set($className, function ($type) { - return new $type([ + $container->set($className, function () { + return new TestClass([ 'prop1' => 100, 'prop2' => 200, ]); @@ -63,7 +48,7 @@ class ServiceLocatorTest extends TestCase $this->assertEquals(200, $object->prop2); // static method - $container = new Container; + $container = new ServiceLocator; $className = TestClass::className(); $container->set($className, [__NAMESPACE__ . "\\Creator", 'create']); $object = $container->get($className); @@ -76,27 +61,18 @@ class ServiceLocatorTest extends TestCase { $object = new TestClass; $className = TestClass::className(); - $container = new Container; + $container = new ServiceLocator; $container->set($className, $object); $this->assertTrue($container->get($className) === $object); } - public function testString() - { - $object = new TestClass; - $className = TestClass::className(); - $container = new Container; - $container->set('test', $object); - $container->set($className, 'test'); - $this->assertTrue($container->get($className) === $object); - } - public function testShared() { // with configuration: shared - $container = new Container; + $container = new ServiceLocator; $className = TestClass::className(); $container->set($className, [ + 'class' => $className, 'prop1' => 10, 'prop2' => 20, ]); @@ -109,43 +85,4 @@ class ServiceLocatorTest extends TestCase $this->assertTrue($object2 instanceof $className); $this->assertTrue($object === $object2); } - - public function testNonShared() - { - // with configuration: non-shared - $container = new Container; - $className = TestClass::className(); - $container->set('*' . $className, [ - 'prop1' => 10, - 'prop2' => 20, - ]); - $object = $container->get($className); - $this->assertEquals(10, $object->prop1); - $this->assertEquals(20, $object->prop2); - $this->assertTrue($object instanceof $className); - // check non-shared - $object2 = $container->get($className); - $this->assertTrue($object2 instanceof $className); - $this->assertTrue($object !== $object2); - - // shared as non-shared - $object = new TestClass; - $className = TestClass::className(); - $container = new Container; - $container->set('*' . $className, $object); - $this->assertTrue($container->get($className) === $object); - } - - public function testRegisterByID() - { - $className = TestClass::className(); - $container = new Container; - $container->set('test', [ - 'class' => $className, - 'prop1' => 100, - ]); - $object = $container->get('test'); - $this->assertTrue($object instanceof TestClass); - $this->assertEquals(100, $object->prop1); - } } -- libgit2 0.27.1