Commit 66abd5b8 by Qiang Xue

Added DI container implementation.

parent a3d6d8ba
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\di;
use yii\base\Component;
/**
* Container implements 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()]].
*
* Container mainly implements setter injection. It does not provide automatic constructor injection
* for performance reason. If you want to support constructor injection, you may override [[buildComponent()]].
*
* Below is an example how a container can be used:
*
* ```php
* namespace app\models;
*
* use yii\base\Object;
* use yii\di\Container;
* use yii\di\Instance;
*
* interface UserFinderInterface
* {
* function findUser();
* }
*
* class UserFinder extends Object implements UserFinderInterface
* {
* public $db;
*
* public function init()
* {
* $this->db = Instance::ensure($this->db, 'yii\db\Connection');
* }
*
* public function findUser()
* {
* }
* }
*
* class UserLister extends Object
* {
* public $finder;
*
* public function init()
* {
* $this->finder = Instance::ensure($this->finder, 'app\models\UserFinderInterface');
* }
* }
*
* $container = new Container;
* $container->components = [
* 'db' => [
* 'class' => 'yii\db\Connection',
* 'dsn' => '...',
* ],
* 'userFinder' => [
* 'class' => 'app\models\UserFinder',
* 'db' => Instance::of('db', $container),
* ],
* 'userLister' => [
* 'finder' => Instance::of('userFinder', $container),
* ],
* ];
*
* // uses both constructor injection (for UserLister)
* // and setter injection (for UserFinder, via Instance::of('db'))
* $lister = $container->get('userLister');
*
* // which is equivalent to:
*
* $db = new \yii\db\Connection(['dsn' => '...']);
* $finder = new UserFinder(['db' => $db]);
* $lister = new UserLister(['finder' => $finder);
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Container extends Component
{
use ContainerTrait;
/**
* Getter magic method.
* This method is overridden to support accessing components like reading properties.
* @param string $name component or property name
* @return mixed the named property value
*/
public function __get($name)
{
if ($this->has($name)) {
return $this->get($name);
} else {
return parent::__get($name);
}
}
/**
* Checks if a property value is null.
* This method overrides the parent implementation by checking if the named component is loaded.
* @param string $name the property name or the event name
* @return boolean whether the property value is null
*/
public function __isset($name)
{
if ($this->has($name)) {
return $this->get($name, [], false) !== null;
} else {
return parent::__isset($name);
}
}
}
<?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 a dependency injection container should implement.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
interface ContainerInterface
{
/**
* Returns the list of the loaded shared component instances.
* @return array the list of the loaded shared component instances (type or ID => component).
*/
public function getComponents();
/**
* Returns the component definitions registered with this container.
* @return array the component definitions registered with this container (type or ID => definition).
*/
public function getComponentDefinitions();
/**
* 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.
*
* @param array $components component definitions or instances
*/
public function setComponents($components);
/**
* Returns a value indicating whether the container has the component definition of the specified type or ID.
* @param string $typeOrID component type (a fully qualified namespaced class/interface name, e.g. `yii\db\Connection`) or ID (e.g. `db`).
* @return boolean whether the container has the component definition of the specified type or ID
* @see set()
*/
public function has($typeOrID);
/**
* 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`).
* @param array $params the named parameters (name => value) to be passed to the object constructor
* if the method needs to create a new object instance.
* @param boolean $create whether to create an instance of a component if it is not previously created.
* This is mainly useful for shared instance.
* @return object|null the component of the specified type or ID, null if the component `$create` is false
* and the component was not instantiated before.
* @throws \yii\base\InvalidConfigException if `$typeOrID` refers to a nonexistent component ID
* or if there is cyclic dependency detected
* @see has()
* @see set()
*/
public function get($typeOrID, $params = [], $create = true);
/**
* Registers a component definition with this container.
*
* 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.
*
* @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 ($container)`, where `$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.
*/
public function set($typeOrID, $definition);
}
<?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 yii\base\InvalidConfigException;
/**
* Instance is a reference to a named component in a container.
*
* You may use [[get()]] to obtain the actual component.
*
* 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.
*
* For example, the following configuration specifies that the "db" property should be
* a component referenced by the "db" component:
*
* ```php
* [
* 'class' => 'app\components\UserFinder',
* 'db' => Instance::of('db'),
* ]
* ```
*
* And in `UserFinder`, you may use `Instance` to make sure the "db" property is properly configured:
*
* ```php
* namespace app\components;
*
* use yii\base\Object;
* use yii\di\Instance;
*
* class UserFinder extends \yii\db\Object
* {
* public $db;
*
* public function init()
* {
* $this->db = Instance::ensure($this->db, 'yii\db\Connection');
* }
* }
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Instance
{
/**
* @var ContainerInterface the container
*/
public $container;
/**
* @var string the component ID
*/
public $id;
/**
* Constructor.
* @param string $id the component ID
* @param ContainerInterface $container the container. If null, the application instance will be used.
*/
protected function __construct($id, ContainerInterface $container = null)
{
$this->id = $id;
$this->container = $container;
}
/**
* Creates a new Instance object.
* @param string $id the component ID
* @param ContainerInterface $container the container. If null, the application instance will be used.
* @return Instance the new Instance object.
*/
public static function of($id, ContainerInterface $container = null)
{
return new self($id, $container);
}
/**
* Ensures that `$value` is an object or a reference to the object of the specified type.
*
* An exception will be thrown if the type is not matched.
*
* Upon success, the method will return the object itself or the object referenced by `$value`.
*
* For example,
*
* ```php
* use yii\db\Connection;
*
* // returns Yii::$app->db
* $db = Instance::ensure('db', Connection::className());
* // or
* $instance = Instance::of('db');
* $db = Instance::ensure($instance, Connection::className());
* ```
*
* @param object|string|static $value 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 null|\yii\base\Component|Instance
* @throws \yii\base\InvalidConfigException
*/
public static function ensure($value, $type, $container = null)
{
if (empty($value)) {
throw new InvalidConfigException('The required component is not specified.');
}
if ($value instanceof $type) {
return $value;
} elseif (is_string($value)) {
$value = new self($value, $container);
}
if ($value instanceof self) {
$component = $value->get();
if ($component instanceof $type) {
return $component;
} else {
$container = $value->container ? : Yii::$app;
if ($component === null) {
throw new InvalidConfigException('"' . $value->id . '" is not a valid component ID of ' . get_class($container));
} else {
throw new InvalidConfigException('"' . $value->id . '" refers to a ' . get_class($component) . " component. $type is expected.");
}
}
} else {
$valueType = is_object($value) ? get_class($value) : gettype($value);
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.
* @throws InvalidConfigException there is no container available
*/
public function get()
{
/** @var ContainerInterface $container */
$container = $this->container ? : Yii::$app;
if ($container !== null) {
return $container->get($this->id);
} else {
throw new InvalidConfigException("Unable to locate a container for component \"{$this->id}\".");
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment