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 /** 428 * Returns the dependencies of the specified class. 429 * @param string $class class name, interface name or alias name 430 * @return array the dependencies of the specified class. 431 */ 432 protected function getDependencies($class) 433 { 434 // 如果已经缓存了其依赖信息,直接返回缓存中的依赖信息 435 if (isset($this->_reflections[$class])) { 436 return [$this->_reflections[$class], $this->_dependencies[$class]]; 437 } 438 // 使用PHP5 的反射机制来获取类的有关信息,主要就是为了获取依赖信息 439 $dependencies = []; 440 $reflection = new ReflectionClass($class); 441 // 通过类的构建函数的参数来了解这个类依赖于哪些单元 442 $constructor = $reflection->getConstructor(); 443 if ($constructor !== null) { 444 foreach ($constructor->getParameters() as $param) { 445 if ($param->isDefaultValueAvailable()) { 446 // 构造函数如果有默认值,将默认值作为依赖 447 $dependencies[] = $param->getDefaultValue(); 448 } else { 449 // 构造函数没有默认值,则为其创建一个引用, 就是前面提到的 Instance 类型。 450 $c = $param->getClass(); 451 $dependencies[] = Instance::of($c === null ? null : $c->getName()); 452 } 453 } 454 } 455 // 将 ReflectionClass 对象缓存起来 456 $this->_reflections[$class] = $reflection; 457 // 将依赖信息缓存起来 458 $this->_dependencies[$class] = $dependencies; 459 460 return [$reflection, $dependencies]; 461 } 462 463 /** 464 * Resolves dependencies by replacing them with the actual object instances. 465 * @param array $dependencies the dependencies 466 * @param ReflectionClass $reflection the class reflection associated with the dependencies 467 * @return array the resolved dependencies 468 * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. 469 */ 470 protected function resolveDependencies($dependencies, $reflection = null) 471 { 472 foreach ($dependencies as $index => $dependency) { 473 // 前面getDependencies() 函数往 $_dependencies[] 中写入的是一个 Instance 数组 474 if ($dependency instanceof Instance) { 475 if ($dependency->id !== null) { 476 // 向容器索要所依赖的实例,递归调用 yii\di\Container::get() 477 $dependencies[$index] = $this->get($dependency->id); 478 } elseif ($reflection !== null) { 479 $name = $reflection->getConstructor()->getParameters()[$index]->getName(); 480 $class = $reflection->getName(); 481 throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\"."); 482 } 483 } 484 } 485 return $dependencies; 486 } 487 488 /** 489 * Invoke a callback with resolving dependencies in parameters. 490 * 解析依赖参数调用回调函数 491 * 492 * This methods allows invoking a callback and let type hinted parameter names to be 493 * resolved as objects of the Container. It additionally allow calling function using named parameters. 494 * 495 * For example, the following callback may be invoked using the Container to resolve the formatter dependency: 496 * 497 * ```php 498 * $formatString = function($string, \yii\i18n\Formatter $formatter) { 499 * // ... 500 * } 501 * Yii::$container->invoke($formatString, [‘string‘ => ‘Hello World!‘]); 502 * ``` 503 * 504 * This will pass the string `‘Hello World!‘` as the first param, and a formatter instance created 505 * by the DI container as the second param to the callable. 506 * 507 * @param callable $callback callable to be invoked. 508 * @param array $params The array of parameters for the function. 509 * This can be either a list of parameters, or an associative array representing named function parameters. 510 * @return mixed the callback return value. 511 * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. 512 * @since 2.0.7 513 */ 514 public function invoke(callable $callback, $params = []) 515 { 516 if (is_callable($callback)) {//函数合法可调用,则解析$params中的依赖参数,调用该函数 517 return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params)); 518 } else {//否则直接以$params为参数调用该函数 519 return call_user_func_array($callback, $params); 520 } 521 } 522 523 /** 524 * Resolve dependencies for a function. 525 * 526 * This method can be used to implement similar functionality as provided by [[invoke()]] in other 527 * components. 528 * 529 * @param callable $callback callable to be invoked. 530 * @param array $params The array of parameters for the function, can be either numeric or associative. 531 * @return array The resolved dependencies. 532 * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. 533 * @since 2.0.7 534 */ 535 public function resolveCallableDependencies(callable $callback, $params = []) 536 { 537 if (is_array($callback)) { 538 $reflection = new \ReflectionMethod($callback[0], $callback[1]); 539 } else { 540 $reflection = new \ReflectionFunction($callback); 541 } 542 543 $args = []; 544 545 $associative = ArrayHelper::isAssociative($params); 546 547 foreach ($reflection->getParameters() as $param) { 548 $name = $param->getName(); 549 if (($class = $param->getClass()) !== null) { 550 $className = $class->getName(); 551 if ($associative && isset($params[$name]) && $params[$name] instanceof $className) { 552 $args[] = $params[$name]; 553 unset($params[$name]); 554 } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) { 555 $args[] = array_shift($params); 556 } elseif (Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { 557 $args[] = $obj; 558 } else { 559 $args[] = $this->get($className); 560 } 561 } elseif ($associative && isset($params[$name])) { 562 $args[] = $params[$name]; 563 unset($params[$name]); 564 } elseif (!$associative && count($params)) { 565 $args[] = array_shift($params); 566 } elseif ($param->isDefaultValueAvailable()) { 567 $args[] = $param->getDefaultValue(); 568 } elseif (!$param->isOptional()) { 569 $funcName = $reflection->getName(); 570 throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\"."); 571 } 572 } 573 574 foreach ($params as $value) { 575 $args[] = $value; 576 } 577 return $args; 578 } 579 }
时间: 2024-10-22 21:48:20