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 | 4041 | public function get($class, $params = [], $config = []) |
|||||||||
162 | { |
||||||||||
163 | 4041 | if ($class instanceof Instance) { |
|||||||||
164 | 1 | $class = $class->id; |
|||||||||
165 | } |
||||||||||
166 | 4041 | if (isset($this->_singletons[$class])) { |
|||||||||
167 | // singleton |
||||||||||
168 | 4 | return $this->_singletons[$class]; |
|||||||||
169 | 4041 | } elseif (!isset($this->_definitions[$class])) { |
|||||||||
170 | 4037 | return $this->build($class, $params, $config); |
|||||||||
171 | } |
||||||||||
172 | |||||||||||
173 | 42 | $definition = $this->_definitions[$class]; |
|||||||||
174 | |||||||||||
175 | 42 | if (is_callable($definition, true)) { |
|||||||||
176 | 9 | $params = $this->resolveDependencies($this->mergeParams($class, $params)); |
|||||||||
177 | 9 | $object = call_user_func($definition, $this, $params, $config); |
|||||||||
178 | 36 | } elseif (is_array($definition)) { |
|||||||||
179 | 35 | $concrete = $definition['class']; |
|||||||||
180 | 35 | unset($definition['class']); |
|||||||||
181 | |||||||||||
182 | 35 | $config = array_merge($definition, $config); |
|||||||||
183 | 35 | $params = $this->mergeParams($class, $params); |
|||||||||
184 | |||||||||||
185 | 35 | if ($concrete === $class) { |
|||||||||
186 | 4 | $object = $this->build($class, $params, $config); |
|||||||||
187 | } else { |
||||||||||
188 | 35 | $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 | 38 | if (array_key_exists($class, $this->_singletons)) { |
|||||||||
197 | // singleton |
||||||||||
198 | 5 | $this->_singletons[$class] = $object; |
|||||||||
199 | } |
||||||||||
200 | |||||||||||
201 | 38 | 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 | 655 | public function set($class, $definition = [], array $params = []) |
|||||||||
267 | { |
||||||||||
268 | 655 | $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); |
|||||||||
269 | 655 | $this->_params[$class] = $params; |
|||||||||
270 | 655 | unset($this->_singletons[$class]); |
|||||||||
271 | 655 | 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 | 9 | public function has($class) |
|||||||||
302 | { |
||||||||||
303 | 9 | 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 | 665 | protected function normalizeDefinition($class, $definition) |
|||||||||
335 | { |
||||||||||
336 | 665 | if (empty($definition)) { |
|||||||||
337 | 1 | return ['class' => $class]; |
|||||||||
338 | 665 | } elseif (is_string($definition)) { |
|||||||||
339 | 10 | return ['class' => $definition]; |
|||||||||
340 | 661 | } elseif ($definition instanceof Instance) { |
|||||||||
341 | 2 | return ['class' => $definition->id]; |
|||||||||
342 | 660 | } elseif (is_callable($definition, true) || is_object($definition)) { |
|||||||||
343 | 629 | return $definition; |
|||||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||||||
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 | public function getDefinitions() |
||||||||||
368 | { |
||||||||||
369 | 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 | 4038 | protected function build($class, $params, $config) |
|||||||||
383 | { |
||||||||||
384 | /* @var $reflection ReflectionClass */ |
||||||||||
385 | 4038 | list($reflection, $dependencies) = $this->getDependencies($class); |
|||||||||
386 | |||||||||||
387 | 4034 | $addDependencies = []; |
|||||||||
388 | 4034 | if (isset($config['__construct()'])) { |
|||||||||
389 | 4 | $addDependencies = $config['__construct()']; |
|||||||||
390 | 4 | unset($config['__construct()']); |
|||||||||
391 | } |
||||||||||
392 | 4034 | foreach ($params as $index => $param) { |
|||||||||
393 | 1287 | $addDependencies[$index] = $param; |
|||||||||
394 | } |
||||||||||
395 | |||||||||||
396 | 4034 | $this->validateDependencies($addDependencies); |
|||||||||
397 | |||||||||||
398 | 4033 | if ($addDependencies && is_int(key($addDependencies))) { |
|||||||||
399 | 1287 | $dependencies = array_values($dependencies); |
|||||||||
400 | 1287 | $dependencies = $this->mergeDependencies($dependencies, $addDependencies); |
|||||||||
401 | } else { |
||||||||||
402 | 4033 | $dependencies = $this->mergeDependencies($dependencies, $addDependencies); |
|||||||||
403 | 4033 | $dependencies = array_values($dependencies); |
|||||||||
404 | } |
||||||||||
405 | |||||||||||
406 | 4033 | $dependencies = $this->resolveDependencies($dependencies, $reflection); |
|||||||||
407 | 4032 | if (!$reflection->isInstantiable()) { |
|||||||||
408 | 4 | throw new NotInstantiableException($reflection->name); |
|||||||||
409 | } |
||||||||||
410 | 4030 | if (empty($config)) { |
|||||||||
411 | 2609 | return $reflection->newInstanceArgs($dependencies); |
|||||||||
412 | } |
||||||||||
413 | |||||||||||
414 | 3848 | $config = $this->resolveDependencies($config); |
|||||||||
415 | |||||||||||
416 | 3848 | if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) { |
|||||||||
417 | // set $config as the last parameter (existing one will be overwritten) |
||||||||||
418 | 3847 | $dependencies[count($dependencies) - 1] = $config; |
|||||||||
419 | 3847 | 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 | 4033 | private function mergeDependencies($a, $b) |
|||||||||
436 | { |
||||||||||
437 | 4033 | foreach ($b as $index => $dependency) { |
|||||||||
438 | 1290 | $a[$index] = $dependency; |
|||||||||
439 | } |
||||||||||
440 | 4033 | return $a; |
|||||||||
441 | } |
||||||||||
442 | |||||||||||
443 | /** |
||||||||||
444 | * @param array $parameters |
||||||||||
445 | * @throws InvalidConfigException |
||||||||||
446 | */ |
||||||||||
447 | 4034 | private function validateDependencies($parameters) |
|||||||||
448 | { |
||||||||||
449 | 4034 | $hasStringParameter = false; |
|||||||||
450 | 4034 | $hasIntParameter = false; |
|||||||||
451 | 4034 | foreach ($parameters as $index => $parameter) { |
|||||||||
452 | 1291 | if (is_string($index)) { |
|||||||||
453 | 4 | $hasStringParameter = true; |
|||||||||
454 | 4 | if ($hasIntParameter) { |
|||||||||
455 | 4 | break; |
|||||||||
456 | } |
||||||||||
457 | } else { |
||||||||||
458 | 1288 | $hasIntParameter = true; |
|||||||||
459 | 1288 | if ($hasStringParameter) { |
|||||||||
460 | 1 | break; |
|||||||||
461 | } |
||||||||||
462 | } |
||||||||||
463 | } |
||||||||||
464 | 4034 | 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 | 41 | protected function mergeParams($class, $params) |
|||||||||
478 | { |
||||||||||
479 | 41 | if (empty($this->_params[$class])) { |
|||||||||
480 | 41 | 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 | 4038 | protected function getDependencies($class) |
|||||||||
500 | { |
||||||||||
501 | 4038 | if (isset($this->_reflections[$class])) { |
|||||||||
502 | 3986 | return [$this->_reflections[$class], $this->_dependencies[$class]]; |
|||||||||
503 | } |
||||||||||
504 | |||||||||||
505 | 206 | $dependencies = []; |
|||||||||
506 | try { |
||||||||||
507 | 206 | $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 | 202 | $constructor = $reflection->getConstructor(); |
|||||||||
518 | 202 | if ($constructor !== null) { |
|||||||||
519 | 199 | foreach ($constructor->getParameters() as $param) { |
|||||||||
520 | 199 | if (PHP_VERSION_ID >= 50600 && $param->isVariadic()) { |
|||||||||
521 | 1 | break; |
|||||||||
522 | } |
||||||||||
523 | |||||||||||
524 | 198 | if (PHP_VERSION_ID >= 80000) { |
|||||||||
525 | 198 | $c = $param->getType(); |
|||||||||
526 | 198 | $isClass = false; |
|||||||||
527 | 198 | if ($c instanceof ReflectionNamedType) { |
|||||||||
528 | 198 | $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 | 198 | $className = $isClass ? $c->getName() : null; |
|||||||||
553 | |||||||||||
554 | 198 | if ($className !== null) { |
|||||||||
555 | 8 | $dependencies[$param->getName()] = Instance::of($className, $this->isNulledParam($param)); |
|||||||||
556 | } else { |
||||||||||
557 | 197 | $dependencies[$param->getName()] = $param->isDefaultValueAvailable() |
|||||||||
558 | 192 | ? $param->getDefaultValue() |
|||||||||
559 | 34 | : null; |
|||||||||
560 | } |
||||||||||
561 | } |
||||||||||
562 | } |
||||||||||
563 | |||||||||||
564 | 202 | $this->_reflections[$class] = $reflection; |
|||||||||
565 | 202 | $this->_dependencies[$class] = $dependencies; |
|||||||||
566 | |||||||||||
567 | 202 | 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 | 4035 | protected function resolveDependencies($dependencies, $reflection = null) |
|||||||||
587 | { |
||||||||||
588 | 4035 | foreach ($dependencies as $index => $dependency) { |
|||||||||
589 | 4029 | 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 | 4027 | } elseif ($this->_resolveArrays && is_array($dependency)) { |
|||||||||
598 | 1 | $dependencies[$index] = $this->resolveDependencies($dependency, $reflection); |
|||||||||
599 | } |
||||||||||
600 | } |
||||||||||
601 | |||||||||||
602 | 4034 | 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)) { |
|||||||||
0 ignored issues
–
show
The type
ReflectionIntersectionType was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||||||
669 | 2 | $isClass = false; |
|||||||||
670 | 2 | foreach ($class->getTypes() as $type) { |
|||||||||
0 ignored issues
–
show
The method
getTypes() does not exist on ReflectionType . It seems like you code against a sub-type of ReflectionType such as ReflectionUnionType .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||||
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
The method
getName() does not exist on ReflectionType . It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() 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. ![]() |
|||||||||||
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 | 10 | public function setDefinitions(array $definitions) |
|||||||||
778 | { |
||||||||||
779 | 10 | foreach ($definitions as $class => $definition) { |
|||||||||
780 | 10 | 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 | 10 | $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 |