Yii源码阅读笔记(三十五)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Yii源码阅读笔记(三十五)相关的知识,希望对你有一定的参考价值。

Container,用于动态地创建、注入依赖单元,映射依赖关系等功能,减少了许多代码量,降低代码耦合程度,提高项目的可维护性。

  1 namespace yii\di;
  2 
  3 use ReflectionClass;
  4 use Yii;
  5 use yii\base\Component;
  6 use yii\base\InvalidConfigException;
  7 use yii\helpers\ArrayHelper;
  8 
  9 /**
 10  * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
 11  *
 12  * A dependency injection (DI) container is an object that knows how to instantiate and configure objects and
 13  * all their dependent objects. For more information about DI, please refer to
 14  * [Martin Fowler‘s article](http://martinfowler.com/articles/injection.html).
 15  *
 16  * Container supports constructor injection as well as property injection.
 17  *
 18  * To use Container, you first need to set up the class dependencies by calling [[set()]].
 19  * You then call [[get()]] to create a new class object. Container will automatically instantiate
 20  * dependent objects, inject them into the object being created, configure and finally return the newly created object.
 21  *
 22  * By default, [[\Yii::$container]] refers to a Container instance which is used by [[\Yii::createObject()]]
 23  * to create new object instances. You may use this method to replace the `new` operator
 24  * when creating a new object, which gives you the benefit of automatic dependency resolution and default
 25  * property configuration.
 26  *
 27  * Below is an example of using Container:
 28  *
 29  * ```php
 30  * namespace app\models;
 31  *
 32  * use yii\base\Object;
 33  * use yii\db\Connection;
 34  * use yii\di\Container;
 35  *
 36  * interface UserFinderInterface
 37  * {
 38  *     function findUser();
 39  * }
 40  *
 41  * class UserFinder extends Object implements UserFinderInterface
 42  * {
 43  *     public $db;
 44  *
 45  *     public function __construct(Connection $db, $config = [])
 46  *     {
 47  *         $this->db = $db;
 48  *         parent::__construct($config);
 49  *     }
 50  *
 51  *     public function findUser()
 52  *     {
 53  *     }
 54  * }
 55  *
 56  * class UserLister extends Object
 57  * {
 58  *     public $finder;
 59  *
 60  *     public function __construct(UserFinderInterface $finder, $config = [])
 61  *     {
 62  *         $this->finder = $finder;
 63  *         parent::__construct($config);
 64  *     }
 65  * }
 66  *
 67  * $container = new Container;
 68  * $container->set(‘yii\db\Connection‘, [
 69  *     ‘dsn‘ => ‘...‘,
 70  * ]);
 71  * $container->set(‘app\models\UserFinderInterface‘, [
 72  *     ‘class‘ => ‘app\models\UserFinder‘,
 73  * ]);
 74  * $container->set(‘userLister‘, ‘app\models\UserLister‘);
 75  *
 76  * $lister = $container->get(‘userLister‘);
 77  *
 78  * // which is equivalent to:
 79  *
 80  * $db = new \yii\db\Connection([‘dsn‘ => ‘...‘]);
 81  * $finder = new UserFinder($db);
 82  * $lister = new UserLister($finder);
 83  * ```
 84  *
 85  * @property array $definitions The list of the object definitions or the loaded shared objects (type or ID =>
 86  * definition or instance). This property is read-only.
 87  *
 88  * @author Qiang Xue <[email protected]>
 89  * @since 2.0
 90  */
 91 class Container extends Component
 92 {
 93     /**
 94      * @var array singleton objects indexed by their types
 95      * @var array 用于保存单例Singleton对象,以对象类型为键(类名、接口名、别名)
 96      */
 97     private $_singletons = [];
 98     /**
 99      * @var array object definitions indexed by their types
100      * @var array 用于保存依赖的定义,以对象类型为键(类名、接口名、别名)
101      */
102     private $_definitions = [];
103     /**
104      * @var array constructor parameters indexed by object types
105      * @var array 用于保存构造函数的参数,以对象类型为键(类名、接口名、别名)
106      */
107     private $_params = [];
108     /**
109      * @var array cached ReflectionClass objects indexed by class/interface names
110      * @var array 用于缓存ReflectionClass对象,以类名或接口名为键
111      */
112     private $_reflections = [];
113     /**
114      * @var array cached dependencies indexed by class/interface names. Each class name
115      * is associated with a list of constructor parameter types or default values.
116      * @var array 用于缓存依赖信息,以类名或接口名为键
117      */
118     private $_dependencies = [];
119 
120 
121     /**
122      * Returns an instance of the requested class.
123      * 返回一个对象或一个别名所代表的对象
124      *
125      * You may provide constructor parameters (`$params`) and object configurations (`$config`)
126      * that will be used during the creation of the instance.
127      *
128      * If the class implements [[\yii\base\Configurable]], the `$config` parameter will be passed as the last
129      * parameter to the class constructor; Otherwise, the configuration will be applied *after* the object is
130      * instantiated.
131      *
132      * Note that if the class is declared to be singleton by calling [[setSingleton()]],
133      * the same instance of the class will be returned each time this method is called.
134      * In this case, the constructor parameters and object configurations will be used
135      * only if the class is instantiated the first time.
136      *
137      * @param string $class the class name or an alias name (e.g. `foo`) that was previously registered via [[set()]]
138      * or [[setSingleton()]].
139      * @param array $params a list of constructor parameter values. The parameters should be provided in the order
140      * they appear in the constructor declaration. If you want to skip some parameters, you should index the remaining
141      * ones with the integers that represent their positions in the constructor parameter list.
142      * @param array $config a list of name-value pairs that will be used to initialize the object properties.
143      * @return object an instance of the requested class.
144      * @throws InvalidConfigException if the class cannot be recognized or correspond to an invalid definition
145      */
146     public function get($class, $params = [], $config = [])
147     {
148         // 已经有一个完成实例化的单例,直接引用这个单例
149         if (isset($this->_singletons[$class])) {
150             // singleton
151             return $this->_singletons[$class];
152             // 如果是尚未注册过的依赖,说明它不依赖其他单元,或者依赖信息不用定义,则根据传入的参数创建一个实例
153         } elseif (!isset($this->_definitions[$class])) {
154             return $this->build($class, $params, $config);
155         }
156         // 创建 $_definitions[$class] 数组的副本
157         $definition = $this->_definitions[$class];
158          // 依赖的定义是个 PHP callable,则调用它
159         if (is_callable($definition, true)) {
160             $params = $this->resolveDependencies($this->mergeParams($class, $params));
161             $object = call_user_func($definition, $this, $params, $config);
162         } elseif (is_array($definition)) { // 依赖的定义是个数组,合并相关的配置和参数,创建之
163             $concrete = $definition[‘class‘];
164             unset($definition[‘class‘]);
165             // 将依赖定义中配置数组和参数数组与传入的配置数组和参数数组合并
166             $config = array_merge($definition, $config);
167             $params = $this->mergeParams($class, $params);
168 
169             if ($concrete === $class) {
170                 // 这是递归终止的重要条件
171                 $object = $this->build($class, $params, $config);
172             } else {
173                 // 这里实现了递归解析
174                 $object = $this->get($concrete, $params, $config);
175             }
176             // 依赖的定义是对象则保存为单例
177         } elseif (is_object($definition)) {
178             return $this->_singletons[$class] = $definition;
179         } else {
180             throw new InvalidConfigException(‘Unexpected object definition type: ‘ . gettype($definition));
181         }
182         // 依赖的定义已经定义为单例的,应当实例化该对象
183         if (array_key_exists($class, $this->_singletons)) {
184             // singleton
185             $this->_singletons[$class] = $object;
186         }
187 
188         return $object;
189     }
190 
191     /**
192      * Registers a class definition with this container.
193      * 用于在每次请求时构造新的实例返回
194      *
195      * For example,
196      *
197      * ```php
198      * // register a class name as is. This can be skipped.
199      * $container->set(‘yii\db\Connection‘);
200      *
201      * // register an interface
202      * // When a class depends on the interface, the corresponding class
203      * // will be instantiated as the dependent object
204      * $container->set(‘yii\mail\MailInterface‘, ‘yii\swiftmailer\Mailer‘);
205      *
206      * // register an alias name. You can use $container->get(‘foo‘)
207      * // to create an instance of Connection
208      * $container->set(‘foo‘, ‘yii\db\Connection‘);
209      *
210      * // register a class with configuration. The configuration
211      * // will be applied when the class is instantiated by get()
212      * $container->set(‘yii\db\Connection‘, [
213      *     ‘dsn‘ => ‘mysql:host=127.0.0.1;dbname=demo‘,
214      *     ‘username‘ => ‘root‘,
215      *     ‘password‘ => ‘‘,
216      *     ‘charset‘ => ‘utf8‘,
217      * ]);
218      *
219      * // register an alias name with class configuration
220      * // In this case, a "class" element is required to specify the class
221      * $container->set(‘db‘, [
222      *     ‘class‘ => ‘yii\db\Connection‘,
223      *     ‘dsn‘ => ‘mysql:host=127.0.0.1;dbname=demo‘,
224      *     ‘username‘ => ‘root‘,
225      *     ‘password‘ => ‘‘,
226      *     ‘charset‘ => ‘utf8‘,
227      * ]);
228      *
229      * // register a PHP callable
230      * // The callable will be executed when $container->get(‘db‘) is called
231      * $container->set(‘db‘, function ($container, $params, $config) {
232      *     return new \yii\db\Connection($config);
233      * });
234      * ```
235      *
236      * If a class definition with the same name already exists, it will be overwritten with the new one.
237      * You may use [[has()]] to check if a class definition already exists.
238      *
239      * @param string $class class name, interface name or alias name
240      * @param mixed $definition the definition associated with `$class`. It can be one of the following:
241      *
242      * - a PHP callable: The callable will be executed when [[get()]] is invoked. The signature of the callable
243      *   should be `function ($container, $params, $config)`, where `$params` stands for the list of constructor
244      *   parameters, `$config` the object configuration, and `$container` the container object. The return value
245      *   of the callable will be returned by [[get()]] as the object instance requested.
246      * - a configuration array: the array contains name-value pairs that will be used to initialize the property
247      *   values of the newly created object when [[get()]] is called. The `class` element stands for the
248      *   the class of the object to be created. If `class` is not specified, `$class` will be used as the class name.
249      * - a string: a class name, an interface name or an alias name.
250      * @param array $params the list of constructor parameters. The parameters will be passed to the class
251      * constructor when [[get()]] is called.
252      * @return $this the container itself
253      */
254     public function set($class, $definition = [], array $params = [])
255     {
256         // 规范化 $definition 并写入 $_definitions[$class]
257         $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
258         // 将构造函数参数写入 $_params[$class]
259         $this->_params[$class] = $params;
260         // 删除$_singletons[$class]
261         unset($this->_singletons[$class]);
262         return $this;
263     }
264 
265     /**
266      * Registers a class definition with this container and marks the class as a singleton class.
267      * 维护一个单例,每次请求时都返回同一对象
268      *
269      * This method is similar to [[set()]] except that classes registered via this method will only have one
270      * instance. Each time [[get()]] is called, the same instance of the specified class will be returned.
271      *
272      * @param string $class class name, interface name or alias name
273      * @param mixed $definition the definition associated with `$class`. See [[set()]] for more details.
274      * @param array $params the list of constructor parameters. The parameters will be passed to the class
275      * constructor when [[get()]] is called.
276      * @return $this the container itself
277      * @see set()
278      */
279     public function setSingleton($class, $definition = [], array $params = [])
280     {
281         // 规范化 $definition 并写入 $_definitions[$class]
282         $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
283         // 将构造函数参数写入 $_params[$class]
284         $this->_params[$class] = $params;
285         // 将$_singleton[$class]置为null,表示还未实例化
286         $this->_singletons[$class] = null;
287         return $this;
288     }
289 
290     /**
291      * Returns a value indicating whether the container has the definition of the specified name.
292      * 判断_definitions中是否定义该依赖
293      * @param string $class class name, interface name or alias name
294      * @return boolean whether the container has the definition of the specified name..
295      * @see set()
296      */
297     public function has($class)
298     {
299         return isset($this->_definitions[$class]);
300     }
301 
302     /**
303      * Returns a value indicating whether the given name corresponds to a registered singleton.
304      * 判断_singletons中是否定义该依赖,如果$checkInstance为真,怎判断该依赖是否实例化
305      * @param string $class class name, interface name or alias name
306      * @param boolean $checkInstance whether to check if the singleton has been instantiated.
307      * @return boolean whether the given name corresponds to a registered singleton. If `$checkInstance` is true,
308      * the method should return a value indicating whether the singleton has been instantiated.
309      */
310     public function hasSingleton($class, $checkInstance = false)
311     {
312         return $checkInstance ? isset($this->_singletons[$class]) : array_key_exists($class, $this->_singletons);
313     }
314 
315     /**
316      * Removes the definition for the specified name.
317      * 移除指定的依赖
318      * @param string $class class name, interface name or alias name
319      */
320     public function clear($class)
321     {
322         unset($this->_definitions[$class], $this->_singletons[$class]);
323     }
324 
325     /**
326      * Normalizes the class definition.
327      * @param string $class class name
328      * @param string|array|callable $definition the class definition
329      * @return array the normalized class definition
330      * @throws InvalidConfigException if the definition is invalid.
331      */
332     protected function normalizeDefinition($class, $definition)
333     {
334         // $definition 是空的转换成 [‘class‘ => $class] 形式
335         if (empty($definition)) {
336             return [‘class‘ => $class];
337         } elseif (is_string($definition)) {// $definition 是字符串,转换成 [‘class‘ => $definition] 形式
338             return [‘class‘ => $definition];
339         } elseif (is_callable($definition, true) || is_object($definition)) {// $definition 是PHP callable 或对象,则直接将其作为依赖的定义
340             return $definition;
341         } elseif (is_array($definition)) { // $definition 是数组则确保该数组定义了 class 元素
342             if (!isset($definition[‘class‘])) {//class 元素未定义
343                 if (strpos($class, ‘\\‘) !== false) {//判断传入的$class是否符合条件
344                     $definition[‘class‘] = $class;//符合则将传入的 $class 作为该元素的值
345                 } else {//否则抛出异常
346                     throw new InvalidConfigException("A class definition requires a \"class\" member.");
347                 }
348             }
349             return $definition;//返回该数组
350         } else {//否则抛出异常
351             throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition));
352         }
353     }
354 
355     /**
356      * Returns the list of the object definitions or the loaded shared objects.
357      * @return array the list of the object definitions or the loaded shared objects (type or ID => definition or instance).
358      */
359     public function getDefinitions()
360     {
361         return $this->_definitions;
362     }
363 
364     /**
365      * Creates an instance of the specified class.
366      * This method will resolve dependencies of the specified class, instantiate them, and inject
367      * them into the new instance of the specified class.
368      * @param string $class the class name
369      * @param array $params constructor parameters
370      * @param array $config configurations to be applied to the new instance
371      * @return object the newly created instance of the specified class
372      */
373     protected function build($class, $params, $config)
374     {
375         /* @var $reflection ReflectionClass */
376         // 调用上面提到的getDependencies来获取并缓存依赖信息,留意这里 list 的用法
377         list ($reflection, $dependencies) = $this->getDependencies($class);
378         // 用传入的 $params 的内容补充、覆盖到依赖信息中
379         foreach ($params as $index => $param) {
380             $dependencies[$index] = $param;
381         }
382         // 解析依赖信息,如果有依赖单元需要提前实例化,会在这一步完成
383         $dependencies = $this->resolveDependencies($dependencies, $reflection);
384         if (empty($config)) {
385             // 实例化这个对象
386             return $reflection->newInstanceArgs($dependencies);
387         }
388 
389         if (!empty($dependencies) && $reflection->implementsInterface(‘yii\base\Configurable‘)) {//$dependencies不为空且实现了Configurable接口
390             // set $config as the last parameter (existing one will be overwritten)
391             // 按照 Configurable 接口的要求,构造函数的最后一个参数为 $config 数组
392             $dependencies[count($dependencies) - 1] = $config;
393             // 实例化这个对象
394             return $reflection->newInstanceArgs($dependencies);
395         } else {
396             //否则实例化这个对象,将配置以属性的形式写入
397             $object = $reflection->newInstanceArgs($dependencies);
398             foreach ($config as $name => $value) {
399                 $object->$name = $value;
400             }
401             return $object;
402         }
403     }
404 
405     /**
406      * Merges the user-specified constructor parameters with the ones registered via [[set()]].
407      * 合并 [[set()]]中的参数和用户在构造函数中指定的参数
408      * @param string $class class name, interface name or alias name
409      * @param array $params the constructor parameters
410      * @return array the merged parameters
411      */
412     protected function mergeParams($class, $params)
413     {
414         if (empty($this->_params[$class])) {
415             return $params;
416         } elseif (empty($params)) {
417             return $this->_params[$class];
418         } else {
419             $ps = $this->_params[$class];
420             foreach ($params as $index => $value) {
421                 $ps[$index] = $value;
422             }
423             return $ps;
424         }
425     }
426 
427     /*以上是关于Yii源码阅读笔记(三十五)的主要内容,如果未能解决你的问题,请参考以下文章

Yii源码阅读笔记(三十四)

Yii源码阅读笔记(三十三)

Yii源码阅读笔记(三十一)

Yii源码阅读笔记(三十二)

yii2源码学习笔记(十五)

Python学习笔记(三十五)struct