1 | <?php |
||||||
2 | /** |
||||||
3 | * @link https://www.yiiframework.com/ |
||||||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||||||
5 | * @license https://www.yiiframework.com/license/ |
||||||
6 | */ |
||||||
7 | |||||||
8 | namespace yii\di; |
||||||
9 | |||||||
10 | use ReflectionClass; |
||||||
11 | use ReflectionException; |
||||||
12 | use ReflectionNamedType; |
||||||
13 | use ReflectionParameter; |
||||||
14 | use Yii; |
||||||
15 | use yii\base\Component; |
||||||
16 | use yii\base\InvalidConfigException; |
||||||
17 | use yii\helpers\ArrayHelper; |
||||||
18 | |||||||
19 | /** |
||||||
20 | * Container implements a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container. |
||||||
21 | * |
||||||
22 | * A dependency injection (DI) container is an object that knows how to instantiate and configure objects and |
||||||
23 | * all their dependent objects. For more information about DI, please refer to |
||||||
24 | * [Martin Fowler's article](https://martinfowler.com/articles/injection.html). |
||||||
25 | * |
||||||
26 | * Container supports constructor injection as well as property injection. |
||||||
27 | * |
||||||
28 | * To use Container, you first need to set up the class dependencies by calling [[set()]]. |
||||||
29 | * You then call [[get()]] to create a new class object. The Container will automatically instantiate |
||||||
30 | * dependent objects, inject them into the object being created, configure, and finally return the newly created object. |
||||||
31 | * |
||||||
32 | * By default, [[\Yii::$container]] refers to a Container instance which is used by [[\Yii::createObject()]] |
||||||
33 | * to create new object instances. You may use this method to replace the `new` operator |
||||||
34 | * when creating a new object, which gives you the benefit of automatic dependency resolution and default |
||||||
35 | * property configuration. |
||||||
36 | * |
||||||
37 | * Below is an example of using Container: |
||||||
38 | * |
||||||
39 | * ```php |
||||||
40 | * namespace app\models; |
||||||
41 | * |
||||||
42 | * use yii\base\BaseObject; |
||||||
43 | * use yii\db\Connection; |
||||||
44 | * use yii\di\Container; |
||||||
45 | * |
||||||
46 | * interface UserFinderInterface |
||||||
47 | * { |
||||||
48 | * function findUser(); |
||||||
49 | * } |
||||||
50 | * |
||||||
51 | * class UserFinder extends BaseObject implements UserFinderInterface |
||||||
52 | * { |
||||||
53 | * public $db; |
||||||
54 | * |
||||||
55 | * public function __construct(Connection $db, $config = []) |
||||||
56 | * { |
||||||
57 | * $this->db = $db; |
||||||
58 | * parent::__construct($config); |
||||||
59 | * } |
||||||
60 | * |
||||||
61 | * public function findUser() |
||||||
62 | * { |
||||||
63 | * } |
||||||
64 | * } |
||||||
65 | * |
||||||
66 | * class UserLister extends BaseObject |
||||||
67 | * { |
||||||
68 | * public $finder; |
||||||
69 | * |
||||||
70 | * public function __construct(UserFinderInterface $finder, $config = []) |
||||||
71 | * { |
||||||
72 | * $this->finder = $finder; |
||||||
73 | * parent::__construct($config); |
||||||
74 | * } |
||||||
75 | * } |
||||||
76 | * |
||||||
77 | * $container = new Container; |
||||||
78 | * $container->set('yii\db\Connection', [ |
||||||
79 | * 'dsn' => '...', |
||||||
80 | * ]); |
||||||
81 | * $container->set('app\models\UserFinderInterface', [ |
||||||
82 | * 'class' => 'app\models\UserFinder', |
||||||
83 | * ]); |
||||||
84 | * $container->set('userLister', 'app\models\UserLister'); |
||||||
85 | * |
||||||
86 | * $lister = $container->get('userLister'); |
||||||
87 | * |
||||||
88 | * // which is equivalent to: |
||||||
89 | * |
||||||
90 | * $db = new \yii\db\Connection(['dsn' => '...']); |
||||||
91 | * $finder = new UserFinder($db); |
||||||
92 | * $lister = new UserLister($finder); |
||||||
93 | * ``` |
||||||
94 | * |
||||||
95 | * For more details and usage information on Container, see the [guide article on di-containers](guide:concept-di-container). |
||||||
96 | * |
||||||
97 | * @property-read array $definitions The list of the object definitions or the loaded shared objects (type or |
||||||
98 | * ID => definition or instance). |
||||||
99 | * @property-write bool $resolveArrays Whether to attempt to resolve elements in array dependencies. |
||||||
100 | * |
||||||
101 | * @author Qiang Xue <[email protected]> |
||||||
102 | * @since 2.0 |
||||||
103 | */ |
||||||
104 | class Container extends Component |
||||||
105 | { |
||||||
106 | /** |
||||||
107 | * @var array singleton objects indexed by their types |
||||||
108 | */ |
||||||
109 | private $_singletons = []; |
||||||
110 | /** |
||||||
111 | * @var array object definitions indexed by their types |
||||||
112 | */ |
||||||
113 | private $_definitions = []; |
||||||
114 | /** |
||||||
115 | * @var array constructor parameters indexed by object types |
||||||
116 | */ |
||||||
117 | private $_params = []; |
||||||
118 | /** |
||||||
119 | * @var array cached ReflectionClass objects indexed by class/interface names |
||||||
120 | */ |
||||||
121 | private $_reflections = []; |
||||||
122 | /** |
||||||
123 | * @var array cached dependencies indexed by class/interface names. Each class name |
||||||
124 | * is associated with a list of constructor parameter types or default values. |
||||||
125 | */ |
||||||
126 | private $_dependencies = []; |
||||||
127 | /** |
||||||
128 | * @var bool whether to attempt to resolve elements in array dependencies |
||||||
129 | */ |
||||||
130 | private $_resolveArrays = false; |
||||||
131 | |||||||
132 | |||||||
133 | /** |
||||||
134 | * Returns an instance of the requested class. |
||||||
135 | * |
||||||
136 | * You may provide constructor parameters (`$params`) and object configurations (`$config`) |
||||||
137 | * that will be used during the creation of the instance. |
||||||
138 | * |
||||||
139 | * If the class implements [[\yii\base\Configurable]], the `$config` parameter will be passed as the last |
||||||
140 | * parameter to the class constructor; Otherwise, the configuration will be applied *after* the object is |
||||||
141 | * instantiated. |
||||||
142 | * |
||||||
143 | * Note that if the class is declared to be singleton by calling [[setSingleton()]], |
||||||
144 | * the same instance of the class will be returned each time this method is called. |
||||||
145 | * In this case, the constructor parameters and object configurations will be used |
||||||
146 | * only if the class is instantiated the first time. |
||||||
147 | * |
||||||
148 | * @param string|Instance $class the class Instance, name, or an alias name (e.g. `foo`) that was previously |
||||||
149 | * registered via [[set()]] or [[setSingleton()]]. |
||||||
150 | * @param array $params a list of constructor parameter values. Use one of two definitions: |
||||||
151 | * - Parameters as name-value pairs, for example: `['posts' => PostRepository::class]`. |
||||||
152 | * - Parameters in the order they appear in the constructor declaration. If you want to skip some parameters, |
||||||
153 | * you should index the remaining ones with the integers that represent their positions in the constructor |
||||||
154 | * parameter list. |
||||||
155 | * Dependencies indexed by name and by position in the same array are not allowed. |
||||||
156 | * @param array $config a list of name-value pairs that will be used to initialize the object properties. |
||||||
157 | * @return object an instance of the requested class. |
||||||
158 | * @throws InvalidConfigException if the class cannot be recognized or correspond to an invalid definition |
||||||
159 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||
160 | */ |
||||||
161 | 4048 | public function get($class, $params = [], $config = []) |
|||||
162 | { |
||||||
163 | 4048 | if ($class instanceof Instance) { |
|||||
164 | 1 | $class = $class->id; |
|||||
165 | } |
||||||
166 | 4048 | if (isset($this->_singletons[$class])) { |
|||||
167 | // singleton |
||||||
168 | 4 | return $this->_singletons[$class]; |
|||||
169 | 4048 | } elseif (!isset($this->_definitions[$class])) { |
|||||
170 | 4045 | return $this->build($class, $params, $config); |
|||||
171 | } |
||||||
172 | |||||||
173 | 41 | $definition = $this->_definitions[$class]; |
|||||
174 | |||||||
175 | 41 | if (is_callable($definition, true)) { |
|||||
176 | 7 | $params = $this->resolveDependencies($this->mergeParams($class, $params)); |
|||||
177 | 7 | $object = call_user_func($definition, $this, $params, $config); |
|||||
178 | 37 | } elseif (is_array($definition)) { |
|||||
179 | 36 | $concrete = $definition['class']; |
|||||
180 | 36 | unset($definition['class']); |
|||||
181 | |||||||
182 | 36 | $config = array_merge($definition, $config); |
|||||
183 | 36 | $params = $this->mergeParams($class, $params); |
|||||
184 | |||||||
185 | 36 | if ($concrete === $class) { |
|||||
186 | 4 | $object = $this->build($class, $params, $config); |
|||||
187 | } else { |
||||||
188 | 36 | $object = $this->get($concrete, $params, $config); |
|||||
189 | } |
||||||
190 | 2 | } elseif (is_object($definition)) { |
|||||
191 | 2 | return $this->_singletons[$class] = $definition; |
|||||
192 | } else { |
||||||
193 | throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition)); |
||||||
194 | } |
||||||
195 | |||||||
196 | 37 | if (array_key_exists($class, $this->_singletons)) { |
|||||
197 | // singleton |
||||||
198 | 5 | $this->_singletons[$class] = $object; |
|||||
199 | } |
||||||
200 | |||||||
201 | 37 | return $object; |
|||||
202 | } |
||||||
203 | |||||||
204 | /** |
||||||
205 | * Registers a class definition with this container. |
||||||
206 | * |
||||||
207 | * For example, |
||||||
208 | * |
||||||
209 | * ```php |
||||||
210 | * // register a class name as is. This can be skipped. |
||||||
211 | * $container->set('yii\db\Connection'); |
||||||
212 | * |
||||||
213 | * // register an interface |
||||||
214 | * // When a class depends on the interface, the corresponding class |
||||||
215 | * // will be instantiated as the dependent object |
||||||
216 | * $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); |
||||||
217 | * |
||||||
218 | * // register an alias name. You can use $container->get('foo') |
||||||
219 | * // to create an instance of Connection |
||||||
220 | * $container->set('foo', 'yii\db\Connection'); |
||||||
221 | * |
||||||
222 | * // register a class with configuration. The configuration |
||||||
223 | * // will be applied when the class is instantiated by get() |
||||||
224 | * $container->set('yii\db\Connection', [ |
||||||
225 | * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||||
226 | * 'username' => 'root', |
||||||
227 | * 'password' => '', |
||||||
228 | * 'charset' => 'utf8', |
||||||
229 | * ]); |
||||||
230 | * |
||||||
231 | * // register an alias name with class configuration |
||||||
232 | * // In this case, a "class" element is required to specify the class |
||||||
233 | * $container->set('db', [ |
||||||
234 | * 'class' => 'yii\db\Connection', |
||||||
235 | * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||||
236 | * 'username' => 'root', |
||||||
237 | * 'password' => '', |
||||||
238 | * 'charset' => 'utf8', |
||||||
239 | * ]); |
||||||
240 | * |
||||||
241 | * // register a PHP callable |
||||||
242 | * // The callable will be executed when $container->get('db') is called |
||||||
243 | * $container->set('db', function ($container, $params, $config) { |
||||||
244 | * return new \yii\db\Connection($config); |
||||||
245 | * }); |
||||||
246 | * ``` |
||||||
247 | * |
||||||
248 | * If a class definition with the same name already exists, it will be overwritten with the new one. |
||||||
249 | * You may use [[has()]] to check if a class definition already exists. |
||||||
250 | * |
||||||
251 | * @param string $class class name, interface name or alias name |
||||||
252 | * @param mixed $definition the definition associated with `$class`. It can be one of the following: |
||||||
253 | * |
||||||
254 | * - a PHP callable: The callable will be executed when [[get()]] is invoked. The signature of the callable |
||||||
255 | * should be `function ($container, $params, $config)`, where `$params` stands for the list of constructor |
||||||
256 | * parameters, `$config` the object configuration, and `$container` the container object. The return value |
||||||
257 | * of the callable will be returned by [[get()]] as the object instance requested. |
||||||
258 | * - a configuration array: the array contains name-value pairs that will be used to initialize the property |
||||||
259 | * values of the newly created object when [[get()]] is called. The `class` element stands for |
||||||
260 | * the class of the object to be created. If `class` is not specified, `$class` will be used as the class name. |
||||||
261 | * - a string: a class name, an interface name or an alias name. |
||||||
262 | * @param array $params the list of constructor parameters. The parameters will be passed to the class |
||||||
263 | * constructor when [[get()]] is called. |
||||||
264 | * @return $this the container itself |
||||||
265 | */ |
||||||
266 | 654 | public function set($class, $definition = [], array $params = []) |
|||||
267 | { |
||||||
268 | 654 | $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); |
|||||
269 | 654 | $this->_params[$class] = $params; |
|||||
270 | 654 | unset($this->_singletons[$class]); |
|||||
271 | 654 | return $this; |
|||||
272 | } |
||||||
273 | |||||||
274 | /** |
||||||
275 | * Registers a class definition with this container and marks the class as a singleton class. |
||||||
276 | * |
||||||
277 | * This method is similar to [[set()]] except that classes registered via this method will only have one |
||||||
278 | * instance. Each time [[get()]] is called, the same instance of the specified class will be returned. |
||||||
279 | * |
||||||
280 | * @param string $class class name, interface name or alias name |
||||||
281 | * @param mixed $definition the definition associated with `$class`. See [[set()]] for more details. |
||||||
282 | * @param array $params the list of constructor parameters. The parameters will be passed to the class |
||||||
283 | * constructor when [[get()]] is called. |
||||||
284 | * @return $this the container itself |
||||||
285 | * @see set() |
||||||
286 | */ |
||||||
287 | 10 | public function setSingleton($class, $definition = [], array $params = []) |
|||||
288 | { |
||||||
289 | 10 | $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); |
|||||
290 | 10 | $this->_params[$class] = $params; |
|||||
291 | 10 | $this->_singletons[$class] = null; |
|||||
292 | 10 | return $this; |
|||||
293 | } |
||||||
294 | |||||||
295 | /** |
||||||
296 | * Returns a value indicating whether the container has the definition of the specified name. |
||||||
297 | * @param string $class class name, interface name or alias name |
||||||
298 | * @return bool Whether the container has the definition of the specified name. |
||||||
299 | * @see set() |
||||||
300 | */ |
||||||
301 | 66 | public function has($class) |
|||||
302 | { |
||||||
303 | 66 | return isset($this->_definitions[$class]); |
|||||
304 | } |
||||||
305 | |||||||
306 | /** |
||||||
307 | * Returns a value indicating whether the given name corresponds to a registered singleton. |
||||||
308 | * @param string $class class name, interface name or alias name |
||||||
309 | * @param bool $checkInstance whether to check if the singleton has been instantiated. |
||||||
310 | * @return bool whether the given name corresponds to a registered singleton. If `$checkInstance` is true, |
||||||
311 | * the method should return a value indicating whether the singleton has been instantiated. |
||||||
312 | */ |
||||||
313 | public function hasSingleton($class, $checkInstance = false) |
||||||
314 | { |
||||||
315 | return $checkInstance ? isset($this->_singletons[$class]) : array_key_exists($class, $this->_singletons); |
||||||
316 | } |
||||||
317 | |||||||
318 | /** |
||||||
319 | * Removes the definition for the specified name. |
||||||
320 | * @param string $class class name, interface name or alias name |
||||||
321 | */ |
||||||
322 | 2 | public function clear($class) |
|||||
323 | { |
||||||
324 | 2 | unset($this->_definitions[$class], $this->_singletons[$class]); |
|||||
325 | } |
||||||
326 | |||||||
327 | /** |
||||||
328 | * Normalizes the class definition. |
||||||
329 | * @param string $class class name |
||||||
330 | * @param string|array|callable $definition the class definition |
||||||
331 | * @return array the normalized class definition |
||||||
332 | * @throws InvalidConfigException if the definition is invalid. |
||||||
333 | */ |
||||||
334 | 664 | protected function normalizeDefinition($class, $definition) |
|||||
335 | { |
||||||
336 | 664 | if (empty($definition)) { |
|||||
337 | 1 | return ['class' => $class]; |
|||||
338 | 664 | } elseif (is_string($definition)) { |
|||||
339 | 10 | return ['class' => $definition]; |
|||||
340 | 660 | } elseif ($definition instanceof Instance) { |
|||||
341 | 2 | return ['class' => $definition->id]; |
|||||
342 | 659 | } elseif (is_callable($definition, true) || is_object($definition)) { |
|||||
343 | 628 | return $definition; |
|||||
344 | 35 | } elseif (is_array($definition)) { |
|||||
345 | 35 | if (!isset($definition['class']) && isset($definition['__class'])) { |
|||||
346 | 3 | $definition['class'] = $definition['__class']; |
|||||
347 | 3 | unset($definition['__class']); |
|||||
348 | } |
||||||
349 | 35 | if (!isset($definition['class'])) { |
|||||
350 | 1 | if (strpos($class, '\\') !== false) { |
|||||
351 | 1 | $definition['class'] = $class; |
|||||
352 | } else { |
||||||
353 | throw new InvalidConfigException('A class definition requires a "class" member.'); |
||||||
354 | } |
||||||
355 | } |
||||||
356 | |||||||
357 | 35 | return $definition; |
|||||
358 | } |
||||||
359 | |||||||
360 | throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition)); |
||||||
361 | } |
||||||
362 | |||||||
363 | /** |
||||||
364 | * Returns the list of the object definitions or the loaded shared objects. |
||||||
365 | * @return array the list of the object definitions or the loaded shared objects (type or ID => definition or instance). |
||||||
366 | */ |
||||||
367 | 1 | public function getDefinitions() |
|||||
368 | { |
||||||
369 | 1 | return $this->_definitions; |
|||||
370 | } |
||||||
371 | |||||||
372 | /** |
||||||
373 | * Creates an instance of the specified class. |
||||||
374 | * This method will resolve dependencies of the specified class, instantiate them, and inject |
||||||
375 | * them into the new instance of the specified class. |
||||||
376 | * @param string $class the class name |
||||||
377 | * @param array $params constructor parameters |
||||||
378 | * @param array $config configurations to be applied to the new instance |
||||||
379 | * @return object the newly created instance of the specified class |
||||||
380 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||
381 | */ |
||||||
382 | 4046 | protected function build($class, $params, $config) |
|||||
383 | { |
||||||
384 | /* @var $reflection ReflectionClass */ |
||||||
385 | 4046 | list($reflection, $dependencies) = $this->getDependencies($class); |
|||||
386 | |||||||
387 | 4042 | $addDependencies = []; |
|||||
388 | 4042 | if (isset($config['__construct()'])) { |
|||||
389 | 4 | $addDependencies = $config['__construct()']; |
|||||
390 | 4 | unset($config['__construct()']); |
|||||
391 | } |
||||||
392 | 4042 | foreach ($params as $index => $param) { |
|||||
393 | 1290 | $addDependencies[$index] = $param; |
|||||
394 | } |
||||||
395 | |||||||
396 | 4042 | $this->validateDependencies($addDependencies); |
|||||
397 | |||||||
398 | 4041 | if ($addDependencies && is_int(key($addDependencies))) { |
|||||
399 | 1290 | $dependencies = array_values($dependencies); |
|||||
400 | 1290 | $dependencies = $this->mergeDependencies($dependencies, $addDependencies); |
|||||
401 | } else { |
||||||
402 | 4041 | $dependencies = $this->mergeDependencies($dependencies, $addDependencies); |
|||||
403 | 4041 | $dependencies = array_values($dependencies); |
|||||
404 | } |
||||||
405 | |||||||
406 | 4041 | $dependencies = $this->resolveDependencies($dependencies, $reflection); |
|||||
407 | 4040 | if (!$reflection->isInstantiable()) { |
|||||
408 | 4 | throw new NotInstantiableException($reflection->name); |
|||||
409 | } |
||||||
410 | 4038 | if (empty($config)) { |
|||||
411 | 2617 | return $reflection->newInstanceArgs($dependencies); |
|||||
412 | } |
||||||
413 | |||||||
414 | 3857 | $config = $this->resolveDependencies($config); |
|||||
415 | |||||||
416 | 3857 | if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) { |
|||||
417 | // set $config as the last parameter (existing one will be overwritten) |
||||||
418 | 3856 | $dependencies[count($dependencies) - 1] = $config; |
|||||
419 | 3856 | return $reflection->newInstanceArgs($dependencies); |
|||||
420 | } |
||||||
421 | |||||||
422 | 1 | $object = $reflection->newInstanceArgs($dependencies); |
|||||
423 | 1 | foreach ($config as $name => $value) { |
|||||
424 | 1 | $object->$name = $value; |
|||||
425 | } |
||||||
426 | |||||||
427 | 1 | return $object; |
|||||
428 | } |
||||||
429 | |||||||
430 | /** |
||||||
431 | * @param array $a |
||||||
432 | * @param array $b |
||||||
433 | * @return array |
||||||
434 | */ |
||||||
435 | 4041 | private function mergeDependencies($a, $b) |
|||||
436 | { |
||||||
437 | 4041 | foreach ($b as $index => $dependency) { |
|||||
438 | 1293 | $a[$index] = $dependency; |
|||||
439 | } |
||||||
440 | 4041 | return $a; |
|||||
441 | } |
||||||
442 | |||||||
443 | /** |
||||||
444 | * @param array $parameters |
||||||
445 | * @throws InvalidConfigException |
||||||
446 | */ |
||||||
447 | 4042 | private function validateDependencies($parameters) |
|||||
448 | { |
||||||
449 | 4042 | $hasStringParameter = false; |
|||||
450 | 4042 | $hasIntParameter = false; |
|||||
451 | 4042 | foreach ($parameters as $index => $parameter) { |
|||||
452 | 1294 | if (is_string($index)) { |
|||||
453 | 4 | $hasStringParameter = true; |
|||||
454 | 4 | if ($hasIntParameter) { |
|||||
455 | 4 | break; |
|||||
456 | } |
||||||
457 | } else { |
||||||
458 | 1291 | $hasIntParameter = true; |
|||||
459 | 1291 | if ($hasStringParameter) { |
|||||
460 | 1 | break; |
|||||
461 | } |
||||||
462 | } |
||||||
463 | } |
||||||
464 | 4042 | if ($hasIntParameter && $hasStringParameter) { |
|||||
465 | 1 | throw new InvalidConfigException( |
|||||
466 | 1 | 'Dependencies indexed by name and by position in the same array are not allowed.' |
|||||
467 | 1 | ); |
|||||
468 | } |
||||||
469 | } |
||||||
470 | |||||||
471 | /** |
||||||
472 | * Merges the user-specified constructor parameters with the ones registered via [[set()]]. |
||||||
473 | * @param string $class class name, interface name or alias name |
||||||
474 | * @param array $params the constructor parameters |
||||||
475 | * @return array the merged parameters |
||||||
476 | */ |
||||||
477 | 40 | protected function mergeParams($class, $params) |
|||||
478 | { |
||||||
479 | 40 | if (empty($this->_params[$class])) { |
|||||
480 | 40 | return $params; |
|||||
481 | 3 | } elseif (empty($params)) { |
|||||
482 | 3 | return $this->_params[$class]; |
|||||
483 | } |
||||||
484 | |||||||
485 | $ps = $this->_params[$class]; |
||||||
486 | foreach ($params as $index => $value) { |
||||||
487 | $ps[$index] = $value; |
||||||
488 | } |
||||||
489 | |||||||
490 | return $ps; |
||||||
491 | } |
||||||
492 | |||||||
493 | /** |
||||||
494 | * Returns the dependencies of the specified class. |
||||||
495 | * @param string $class class name, interface name or alias name |
||||||
496 | * @return array the dependencies of the specified class. |
||||||
497 | * @throws NotInstantiableException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||
498 | */ |
||||||
499 | 4046 | protected function getDependencies($class) |
|||||
500 | { |
||||||
501 | 4046 | if (isset($this->_reflections[$class])) { |
|||||
502 | 3995 | return [$this->_reflections[$class], $this->_dependencies[$class]]; |
|||||
503 | } |
||||||
504 | |||||||
505 | 205 | $dependencies = []; |
|||||
506 | try { |
||||||
507 | 205 | $reflection = new ReflectionClass($class); |
|||||
508 | 8 | } catch (\ReflectionException $e) { |
|||||
509 | 8 | throw new NotInstantiableException( |
|||||
510 | 8 | $class, |
|||||
511 | 8 | 'Failed to instantiate component or class "' . $class . '".', |
|||||
512 | 8 | 0, |
|||||
513 | 8 | $e |
|||||
514 | 8 | ); |
|||||
515 | } |
||||||
516 | |||||||
517 | 201 | $constructor = $reflection->getConstructor(); |
|||||
518 | 201 | if ($constructor !== null) { |
|||||
519 | 198 | foreach ($constructor->getParameters() as $param) { |
|||||
520 | 198 | if (PHP_VERSION_ID >= 50600 && $param->isVariadic()) { |
|||||
521 | 1 | break; |
|||||
522 | } |
||||||
523 | |||||||
524 | 197 | if (PHP_VERSION_ID >= 80000) { |
|||||
525 | 197 | $c = $param->getType(); |
|||||
526 | 197 | $isClass = false; |
|||||
527 | 197 | if ($c instanceof ReflectionNamedType) { |
|||||
528 | 197 | $isClass = !$c->isBuiltin(); |
|||||
529 | } |
||||||
530 | } else { |
||||||
531 | try { |
||||||
532 | $c = $param->getClass(); |
||||||
533 | } catch (ReflectionException $e) { |
||||||
534 | if (!$this->isNulledParam($param)) { |
||||||
535 | $notInstantiableClass = null; |
||||||
536 | if (PHP_VERSION_ID >= 70000) { |
||||||
537 | $type = $param->getType(); |
||||||
538 | if ($type instanceof ReflectionNamedType) { |
||||||
539 | $notInstantiableClass = $type->getName(); |
||||||
540 | } |
||||||
541 | } |
||||||
542 | throw new NotInstantiableException( |
||||||
543 | $notInstantiableClass, |
||||||
544 | $notInstantiableClass === null ? 'Can not instantiate unknown class.' : null |
||||||
545 | ); |
||||||
546 | } else { |
||||||
547 | $c = null; |
||||||
548 | } |
||||||
549 | } |
||||||
550 | $isClass = $c !== null; |
||||||
551 | } |
||||||
552 | 197 | $className = $isClass ? $c->getName() : null; |
|||||
553 | |||||||
554 | 197 | if ($className !== null) { |
|||||
555 | 8 | $dependencies[$param->getName()] = Instance::of($className, $this->isNulledParam($param)); |
|||||
556 | } else { |
||||||
557 | 196 | $dependencies[$param->getName()] = $param->isDefaultValueAvailable() |
|||||
558 | 191 | ? $param->getDefaultValue() |
|||||
559 | 34 | : null; |
|||||
560 | } |
||||||
561 | } |
||||||
562 | } |
||||||
563 | |||||||
564 | 201 | $this->_reflections[$class] = $reflection; |
|||||
565 | 201 | $this->_dependencies[$class] = $dependencies; |
|||||
566 | |||||||
567 | 201 | return [$reflection, $dependencies]; |
|||||
568 | } |
||||||
569 | |||||||
570 | /** |
||||||
571 | * @param ReflectionParameter $param |
||||||
572 | * @return bool |
||||||
573 | */ |
||||||
574 | 8 | private function isNulledParam($param) |
|||||
575 | { |
||||||
576 | 8 | return $param->isOptional() || (PHP_VERSION_ID >= 70100 && $param->getType()->allowsNull()); |
|||||
577 | } |
||||||
578 | |||||||
579 | /** |
||||||
580 | * Resolves dependencies by replacing them with the actual object instances. |
||||||
581 | * @param array $dependencies the dependencies |
||||||
582 | * @param ReflectionClass|null $reflection the class reflection associated with the dependencies |
||||||
583 | * @return array the resolved dependencies |
||||||
584 | * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||
585 | */ |
||||||
586 | 4042 | protected function resolveDependencies($dependencies, $reflection = null) |
|||||
587 | { |
||||||
588 | 4042 | foreach ($dependencies as $index => $dependency) { |
|||||
589 | 4037 | if ($dependency instanceof Instance) { |
|||||
590 | 9 | if ($dependency->id !== null) { |
|||||
591 | 9 | $dependencies[$index] = $dependency->get($this); |
|||||
592 | } elseif ($reflection !== null) { |
||||||
593 | $name = $reflection->getConstructor()->getParameters()[$index]->getName(); |
||||||
594 | $class = $reflection->getName(); |
||||||
595 | 7 | throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\"."); |
|||||
596 | } |
||||||
597 | 4035 | } elseif ($this->_resolveArrays && is_array($dependency)) { |
|||||
598 | 1 | $dependencies[$index] = $this->resolveDependencies($dependency, $reflection); |
|||||
599 | } |
||||||
600 | } |
||||||
601 | |||||||
602 | 4041 | return $dependencies; |
|||||
603 | } |
||||||
604 | |||||||
605 | /** |
||||||
606 | * Invoke a callback with resolving dependencies in parameters. |
||||||
607 | * |
||||||
608 | * This method allows invoking a callback and let type hinted parameter names to be |
||||||
609 | * resolved as objects of the Container. It additionally allows calling function using named parameters. |
||||||
610 | * |
||||||
611 | * For example, the following callback may be invoked using the Container to resolve the formatter dependency: |
||||||
612 | * |
||||||
613 | * ```php |
||||||
614 | * $formatString = function($string, \yii\i18n\Formatter $formatter) { |
||||||
615 | * // ... |
||||||
616 | * } |
||||||
617 | * Yii::$container->invoke($formatString, ['string' => 'Hello World!']); |
||||||
618 | * ``` |
||||||
619 | * |
||||||
620 | * This will pass the string `'Hello World!'` as the first param, and a formatter instance created |
||||||
621 | * by the DI container as the second param to the callable. |
||||||
622 | * |
||||||
623 | * @param callable $callback callable to be invoked. |
||||||
624 | * @param array $params The array of parameters for the function. |
||||||
625 | * This can be either a list of parameters, or an associative array representing named function parameters. |
||||||
626 | * @return mixed the callback return value. |
||||||
627 | * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||
628 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||
629 | * @since 2.0.7 |
||||||
630 | */ |
||||||
631 | 10 | public function invoke(callable $callback, $params = []) |
|||||
632 | { |
||||||
633 | 10 | return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params)); |
|||||
634 | } |
||||||
635 | |||||||
636 | /** |
||||||
637 | * Resolve dependencies for a function. |
||||||
638 | * |
||||||
639 | * This method can be used to implement similar functionality as provided by [[invoke()]] in other |
||||||
640 | * components. |
||||||
641 | * |
||||||
642 | * @param callable $callback callable to be invoked. |
||||||
643 | * @param array $params The array of parameters for the function, can be either numeric or associative. |
||||||
644 | * @return array The resolved dependencies. |
||||||
645 | * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||
646 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||
647 | * @since 2.0.7 |
||||||
648 | */ |
||||||
649 | 13 | public function resolveCallableDependencies(callable $callback, $params = []) |
|||||
650 | { |
||||||
651 | 13 | if (is_array($callback)) { |
|||||
652 | 4 | $reflection = new \ReflectionMethod($callback[0], $callback[1]); |
|||||
653 | 11 | } elseif (is_object($callback) && !$callback instanceof \Closure) { |
|||||
654 | 1 | $reflection = new \ReflectionMethod($callback, '__invoke'); |
|||||
655 | } else { |
||||||
656 | 11 | $reflection = new \ReflectionFunction($callback); |
|||||
657 | } |
||||||
658 | |||||||
659 | 13 | $args = []; |
|||||
660 | |||||||
661 | 13 | $associative = ArrayHelper::isAssociative($params); |
|||||
662 | |||||||
663 | 13 | foreach ($reflection->getParameters() as $param) { |
|||||
664 | 8 | $name = $param->getName(); |
|||||
665 | |||||||
666 | 8 | if (PHP_VERSION_ID >= 80000) { |
|||||
667 | 8 | $class = $param->getType(); |
|||||
668 | 8 | if ($class instanceof \ReflectionUnionType || (PHP_VERSION_ID >= 80100 && $class instanceof \ReflectionIntersectionType)) { |
|||||
669 | 2 | $isClass = false; |
|||||
670 | 2 | foreach ($class->getTypes() as $type) { |
|||||
671 | 2 | if (!$type->isBuiltin()) { |
|||||
672 | 2 | $class = $type; |
|||||
673 | 2 | $isClass = true; |
|||||
674 | 2 | break; |
|||||
675 | } |
||||||
676 | } |
||||||
677 | } else { |
||||||
678 | 8 | $isClass = $class !== null && !$class->isBuiltin(); |
|||||
679 | } |
||||||
680 | } else { |
||||||
681 | $class = $param->getClass(); |
||||||
682 | $isClass = $class !== null; |
||||||
683 | } |
||||||
684 | |||||||
685 | 8 | if ($isClass) { |
|||||
686 | 6 | $className = $class->getName(); |
|||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
The method
getName() does not exist on ReflectionUnionType .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
687 | 6 | if (PHP_VERSION_ID >= 50600 && $param->isVariadic()) { |
|||||
688 | 1 | $args = array_merge($args, array_values($params)); |
|||||
689 | 1 | break; |
|||||
690 | } |
||||||
691 | |||||||
692 | 5 | if ($associative && isset($params[$name]) && $params[$name] instanceof $className) { |
|||||
693 | $args[] = $params[$name]; |
||||||
694 | unset($params[$name]); |
||||||
695 | 5 | } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) { |
|||||
696 | 1 | $args[] = array_shift($params); |
|||||
697 | 5 | } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { |
|||||
698 | 1 | $args[] = $obj; |
|||||
699 | } else { |
||||||
700 | // If the argument is optional we catch not instantiable exceptions |
||||||
701 | try { |
||||||
702 | 5 | $args[] = $this->get($className); |
|||||
703 | 1 | } catch (NotInstantiableException $e) { |
|||||
704 | 1 | if ($param->isDefaultValueAvailable()) { |
|||||
705 | 1 | $args[] = $param->getDefaultValue(); |
|||||
706 | } else { |
||||||
707 | 5 | throw $e; |
|||||
708 | } |
||||||
709 | } |
||||||
710 | } |
||||||
711 | 4 | } elseif ($associative && isset($params[$name])) { |
|||||
712 | 2 | $args[] = $params[$name]; |
|||||
713 | 2 | unset($params[$name]); |
|||||
714 | 4 | } elseif (!$associative && count($params)) { |
|||||
715 | 3 | $args[] = array_shift($params); |
|||||
716 | 3 | } elseif ($param->isDefaultValueAvailable()) { |
|||||
717 | 3 | $args[] = $param->getDefaultValue(); |
|||||
718 | } elseif (!$param->isOptional()) { |
||||||
719 | $funcName = $reflection->getName(); |
||||||
720 | throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\"."); |
||||||
721 | } |
||||||
722 | } |
||||||
723 | |||||||
724 | 13 | foreach ($params as $value) { |
|||||
725 | $args[] = $value; |
||||||
726 | } |
||||||
727 | |||||||
728 | 13 | return $args; |
|||||
729 | } |
||||||
730 | |||||||
731 | /** |
||||||
732 | * Registers class definitions within this container. |
||||||
733 | * |
||||||
734 | * @param array $definitions array of definitions. There are two allowed formats of array. |
||||||
735 | * The first format: |
||||||
736 | * - key: class name, interface name or alias name. The key will be passed to the [[set()]] method |
||||||
737 | * as a first argument `$class`. |
||||||
738 | * - value: the definition associated with `$class`. Possible values are described in |
||||||
739 | * [[set()]] documentation for the `$definition` parameter. Will be passed to the [[set()]] method |
||||||
740 | * as the second argument `$definition`. |
||||||
741 | * |
||||||
742 | * Example: |
||||||
743 | * ```php |
||||||
744 | * $container->setDefinitions([ |
||||||
745 | * 'yii\web\Request' => 'app\components\Request', |
||||||
746 | * 'yii\web\Response' => [ |
||||||
747 | * 'class' => 'app\components\Response', |
||||||
748 | * 'format' => 'json' |
||||||
749 | * ], |
||||||
750 | * 'foo\Bar' => function () { |
||||||
751 | * $qux = new Qux; |
||||||
752 | * $foo = new Foo($qux); |
||||||
753 | * return new Bar($foo); |
||||||
754 | * } |
||||||
755 | * ]); |
||||||
756 | * ``` |
||||||
757 | * |
||||||
758 | * The second format: |
||||||
759 | * - key: class name, interface name or alias name. The key will be passed to the [[set()]] method |
||||||
760 | * as a first argument `$class`. |
||||||
761 | * - value: array of two elements. The first element will be passed the [[set()]] method as the |
||||||
762 | * second argument `$definition`, the second one — as `$params`. |
||||||
763 | * |
||||||
764 | * Example: |
||||||
765 | * ```php |
||||||
766 | * $container->setDefinitions([ |
||||||
767 | * 'foo\Bar' => [ |
||||||
768 | * ['class' => 'app\Bar'], |
||||||
769 | * [Instance::of('baz')] |
||||||
770 | * ] |
||||||
771 | * ]); |
||||||
772 | * ``` |
||||||
773 | * |
||||||
774 | * @see set() to know more about possible values of definitions |
||||||
775 | * @since 2.0.11 |
||||||
776 | */ |
||||||
777 | 9 | public function setDefinitions(array $definitions) |
|||||
778 | { |
||||||
779 | 9 | foreach ($definitions as $class => $definition) { |
|||||
780 | 9 | if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition && is_array($definition[1])) { |
|||||
781 | 1 | $this->set($class, $definition[0], $definition[1]); |
|||||
782 | 1 | continue; |
|||||
783 | } |
||||||
784 | |||||||
785 | 9 | $this->set($class, $definition); |
|||||
786 | } |
||||||
787 | } |
||||||
788 | |||||||
789 | /** |
||||||
790 | * Registers class definitions as singletons within this container by calling [[setSingleton()]]. |
||||||
791 | * |
||||||
792 | * @param array $singletons array of singleton definitions. See [[setDefinitions()]] |
||||||
793 | * for allowed formats of array. |
||||||
794 | * |
||||||
795 | * @see setDefinitions() for allowed formats of $singletons parameter |
||||||
796 | * @see setSingleton() to know more about possible values of definitions |
||||||
797 | * @since 2.0.11 |
||||||
798 | */ |
||||||
799 | 10 | public function setSingletons(array $singletons) |
|||||
800 | { |
||||||
801 | 10 | foreach ($singletons as $class => $definition) { |
|||||
802 | 10 | if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition) { |
|||||
803 | 1 | $this->setSingleton($class, $definition[0], $definition[1]); |
|||||
804 | 1 | continue; |
|||||
805 | } |
||||||
806 | |||||||
807 | 10 | $this->setSingleton($class, $definition); |
|||||
808 | } |
||||||
809 | } |
||||||
810 | |||||||
811 | /** |
||||||
812 | * @param bool $value whether to attempt to resolve elements in array dependencies |
||||||
813 | * @since 2.0.37 |
||||||
814 | */ |
||||||
815 | 1 | public function setResolveArrays($value) |
|||||
816 | { |
||||||
817 | 1 | $this->_resolveArrays = (bool) $value; |
|||||
818 | } |
||||||
819 | } |
||||||
820 |