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 | * @template T of object |
||||||||||
162 | * @psalm-param string|class-string<T>|Instance $class |
||||||||||
163 | * @phpstan-param string|class-string<T>|Instance $class |
||||||||||
164 | * @psalm-return ($class is class-string<T> ? T : object) |
||||||||||
165 | * @phpstan-return ($class is class-string<T> ? T : object) |
||||||||||
166 | */ |
||||||||||
167 | 1595 | public function get($class, $params = [], $config = []) |
|||||||||
168 | { |
||||||||||
169 | 1595 | if ($class instanceof Instance) { |
|||||||||
170 | 1 | $class = $class->id; |
|||||||||
171 | } |
||||||||||
172 | 1595 | if (isset($this->_singletons[$class])) { |
|||||||||
173 | // singleton |
||||||||||
174 | 4 | return $this->_singletons[$class]; |
|||||||||
175 | 1595 | } elseif (!isset($this->_definitions[$class])) { |
|||||||||
176 | 1591 | return $this->build($class, $params, $config); |
|||||||||
177 | } |
||||||||||
178 | |||||||||||
179 | 31 | $definition = $this->_definitions[$class]; |
|||||||||
180 | |||||||||||
181 | 31 | if (is_callable($definition, true)) { |
|||||||||
182 | 9 | $params = $this->resolveDependencies($this->mergeParams($class, $params)); |
|||||||||
183 | 9 | $object = call_user_func($definition, $this, $params, $config); |
|||||||||
184 | 25 | } elseif (is_array($definition)) { |
|||||||||
185 | 24 | $concrete = $definition['class']; |
|||||||||
186 | 24 | unset($definition['class']); |
|||||||||
187 | |||||||||||
188 | 24 | $config = array_merge($definition, $config); |
|||||||||
189 | 24 | $params = $this->mergeParams($class, $params); |
|||||||||
190 | |||||||||||
191 | 24 | if ($concrete === $class) { |
|||||||||
192 | 4 | $object = $this->build($class, $params, $config); |
|||||||||
193 | } else { |
||||||||||
194 | 24 | $object = $this->get($concrete, $params, $config); |
|||||||||
195 | } |
||||||||||
196 | 2 | } elseif (is_object($definition)) { |
|||||||||
197 | 2 | return $this->_singletons[$class] = $definition; |
|||||||||
198 | } else { |
||||||||||
199 | throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition)); |
||||||||||
200 | } |
||||||||||
201 | |||||||||||
202 | 27 | if (array_key_exists($class, $this->_singletons)) { |
|||||||||
203 | // singleton |
||||||||||
204 | 4 | $this->_singletons[$class] = $object; |
|||||||||
205 | } |
||||||||||
206 | |||||||||||
207 | 27 | return $object; |
|||||||||
208 | } |
||||||||||
209 | |||||||||||
210 | /** |
||||||||||
211 | * Registers a class definition with this container. |
||||||||||
212 | * |
||||||||||
213 | * For example, |
||||||||||
214 | * |
||||||||||
215 | * ```php |
||||||||||
216 | * // register a class name as is. This can be skipped. |
||||||||||
217 | * $container->set('yii\db\Connection'); |
||||||||||
218 | * |
||||||||||
219 | * // register an interface |
||||||||||
220 | * // When a class depends on the interface, the corresponding class |
||||||||||
221 | * // will be instantiated as the dependent object |
||||||||||
222 | * $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); |
||||||||||
223 | * |
||||||||||
224 | * // register an alias name. You can use $container->get('foo') |
||||||||||
225 | * // to create an instance of Connection |
||||||||||
226 | * $container->set('foo', 'yii\db\Connection'); |
||||||||||
227 | * |
||||||||||
228 | * // register a class with configuration. The configuration |
||||||||||
229 | * // will be applied when the class is instantiated by get() |
||||||||||
230 | * $container->set('yii\db\Connection', [ |
||||||||||
231 | * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||||||||
232 | * 'username' => 'root', |
||||||||||
233 | * 'password' => '', |
||||||||||
234 | * 'charset' => 'utf8', |
||||||||||
235 | * ]); |
||||||||||
236 | * |
||||||||||
237 | * // register an alias name with class configuration |
||||||||||
238 | * // In this case, a "class" element is required to specify the class |
||||||||||
239 | * $container->set('db', [ |
||||||||||
240 | * 'class' => 'yii\db\Connection', |
||||||||||
241 | * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||||||||
242 | * 'username' => 'root', |
||||||||||
243 | * 'password' => '', |
||||||||||
244 | * 'charset' => 'utf8', |
||||||||||
245 | * ]); |
||||||||||
246 | * |
||||||||||
247 | * // register a PHP callable |
||||||||||
248 | * // The callable will be executed when $container->get('db') is called |
||||||||||
249 | * $container->set('db', function ($container, $params, $config) { |
||||||||||
250 | * return new \yii\db\Connection($config); |
||||||||||
251 | * }); |
||||||||||
252 | * ``` |
||||||||||
253 | * |
||||||||||
254 | * If a class definition with the same name already exists, it will be overwritten with the new one. |
||||||||||
255 | * You may use [[has()]] to check if a class definition already exists. |
||||||||||
256 | * |
||||||||||
257 | * @param string $class class name, interface name or alias name |
||||||||||
258 | * @param mixed $definition the definition associated with `$class`. It can be one of the following: |
||||||||||
259 | * |
||||||||||
260 | * - a PHP callable: The callable will be executed when [[get()]] is invoked. The signature of the callable |
||||||||||
261 | * should be `function ($container, $params, $config)`, where `$params` stands for the list of constructor |
||||||||||
262 | * parameters, `$config` the object configuration, and `$container` the container object. The return value |
||||||||||
263 | * of the callable will be returned by [[get()]] as the object instance requested. |
||||||||||
264 | * - a configuration array: the array contains name-value pairs that will be used to initialize the property |
||||||||||
265 | * values of the newly created object when [[get()]] is called. The `class` element stands for |
||||||||||
266 | * the class of the object to be created. If `class` is not specified, `$class` will be used as the class name. |
||||||||||
267 | * - a string: a class name, an interface name or an alias name. |
||||||||||
268 | * @param array $params the list of constructor parameters. The parameters will be passed to the class |
||||||||||
269 | * constructor when [[get()]] is called. |
||||||||||
270 | * @return $this the container itself |
||||||||||
271 | */ |
||||||||||
272 | 26 | public function set($class, $definition = [], array $params = []) |
|||||||||
273 | { |
||||||||||
274 | 26 | $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); |
|||||||||
275 | 26 | $this->_params[$class] = $params; |
|||||||||
276 | 26 | unset($this->_singletons[$class]); |
|||||||||
277 | 26 | return $this; |
|||||||||
278 | } |
||||||||||
279 | |||||||||||
280 | /** |
||||||||||
281 | * Registers a class definition with this container and marks the class as a singleton class. |
||||||||||
282 | * |
||||||||||
283 | * This method is similar to [[set()]] except that classes registered via this method will only have one |
||||||||||
284 | * instance. Each time [[get()]] is called, the same instance of the specified class will be returned. |
||||||||||
285 | * |
||||||||||
286 | * @param string $class class name, interface name or alias name |
||||||||||
287 | * @param mixed $definition the definition associated with `$class`. See [[set()]] for more details. |
||||||||||
288 | * @param array $params the list of constructor parameters. The parameters will be passed to the class |
||||||||||
289 | * constructor when [[get()]] is called. |
||||||||||
290 | * @return $this the container itself |
||||||||||
291 | * @see set() |
||||||||||
292 | */ |
||||||||||
293 | 4 | public function setSingleton($class, $definition = [], array $params = []) |
|||||||||
294 | { |
||||||||||
295 | 4 | $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); |
|||||||||
296 | 4 | $this->_params[$class] = $params; |
|||||||||
297 | 4 | $this->_singletons[$class] = null; |
|||||||||
298 | 4 | return $this; |
|||||||||
299 | } |
||||||||||
300 | |||||||||||
301 | /** |
||||||||||
302 | * Returns a value indicating whether the container has the definition of the specified name. |
||||||||||
303 | * @param string $class class name, interface name or alias name |
||||||||||
304 | * @return bool Whether the container has the definition of the specified name. |
||||||||||
305 | * @see set() |
||||||||||
306 | */ |
||||||||||
307 | 9 | public function has($class) |
|||||||||
308 | { |
||||||||||
309 | 9 | return isset($this->_definitions[$class]); |
|||||||||
310 | } |
||||||||||
311 | |||||||||||
312 | /** |
||||||||||
313 | * Returns a value indicating whether the given name corresponds to a registered singleton. |
||||||||||
314 | * @param string $class class name, interface name or alias name |
||||||||||
315 | * @param bool $checkInstance whether to check if the singleton has been instantiated. |
||||||||||
316 | * @return bool whether the given name corresponds to a registered singleton. If `$checkInstance` is true, |
||||||||||
317 | * the method should return a value indicating whether the singleton has been instantiated. |
||||||||||
318 | */ |
||||||||||
319 | public function hasSingleton($class, $checkInstance = false) |
||||||||||
320 | { |
||||||||||
321 | return $checkInstance ? isset($this->_singletons[$class]) : array_key_exists($class, $this->_singletons); |
||||||||||
322 | } |
||||||||||
323 | |||||||||||
324 | /** |
||||||||||
325 | * Removes the definition for the specified name. |
||||||||||
326 | * @param string $class class name, interface name or alias name |
||||||||||
327 | */ |
||||||||||
328 | 2 | public function clear($class) |
|||||||||
329 | { |
||||||||||
330 | 2 | unset($this->_definitions[$class], $this->_singletons[$class]); |
|||||||||
331 | } |
||||||||||
332 | |||||||||||
333 | /** |
||||||||||
334 | * Normalizes the class definition. |
||||||||||
335 | * @param string $class class name |
||||||||||
336 | * @param string|array|callable $definition the class definition |
||||||||||
337 | * @return array the normalized class definition |
||||||||||
338 | * @throws InvalidConfigException if the definition is invalid. |
||||||||||
339 | */ |
||||||||||
340 | 30 | protected function normalizeDefinition($class, $definition) |
|||||||||
341 | { |
||||||||||
342 | 30 | if (empty($definition)) { |
|||||||||
343 | 1 | return ['class' => $class]; |
|||||||||
344 | 30 | } elseif (is_string($definition)) { |
|||||||||
345 | 10 | return ['class' => $definition]; |
|||||||||
346 | 26 | } elseif ($definition instanceof Instance) { |
|||||||||
347 | 2 | return ['class' => $definition->id]; |
|||||||||
348 | 25 | } elseif (is_callable($definition, true) || is_object($definition)) { |
|||||||||
349 | 10 | return $definition; |
|||||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||||||
350 | 19 | } elseif (is_array($definition)) { |
|||||||||
351 | 19 | if (!isset($definition['class']) && isset($definition['__class'])) { |
|||||||||
352 | 3 | $definition['class'] = $definition['__class']; |
|||||||||
353 | 3 | unset($definition['__class']); |
|||||||||
354 | } |
||||||||||
355 | 19 | if (!isset($definition['class'])) { |
|||||||||
356 | 1 | if (strpos($class, '\\') !== false) { |
|||||||||
357 | 1 | $definition['class'] = $class; |
|||||||||
358 | } else { |
||||||||||
359 | throw new InvalidConfigException('A class definition requires a "class" member.'); |
||||||||||
360 | } |
||||||||||
361 | } |
||||||||||
362 | |||||||||||
363 | 19 | return $definition; |
|||||||||
364 | } |
||||||||||
365 | |||||||||||
366 | throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition)); |
||||||||||
367 | } |
||||||||||
368 | |||||||||||
369 | /** |
||||||||||
370 | * Returns the list of the object definitions or the loaded shared objects. |
||||||||||
371 | * @return array the list of the object definitions or the loaded shared objects (type or ID => definition or instance). |
||||||||||
372 | */ |
||||||||||
373 | public function getDefinitions() |
||||||||||
374 | { |
||||||||||
375 | return $this->_definitions; |
||||||||||
376 | } |
||||||||||
377 | |||||||||||
378 | /** |
||||||||||
379 | * Creates an instance of the specified class. |
||||||||||
380 | * This method will resolve dependencies of the specified class, instantiate them, and inject |
||||||||||
381 | * them into the new instance of the specified class. |
||||||||||
382 | * @param string $class the class name |
||||||||||
383 | * @param array $params constructor parameters |
||||||||||
384 | * @param array $config configurations to be applied to the new instance |
||||||||||
385 | * @return object the newly created instance of the specified class |
||||||||||
386 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||||||
387 | */ |
||||||||||
388 | 1592 | protected function build($class, $params, $config) |
|||||||||
389 | { |
||||||||||
390 | /** @var ReflectionClass $reflection */ |
||||||||||
391 | 1592 | list($reflection, $dependencies) = $this->getDependencies($class); |
|||||||||
392 | |||||||||||
393 | 1588 | $addDependencies = []; |
|||||||||
394 | 1588 | if (isset($config['__construct()'])) { |
|||||||||
395 | 6 | $addDependencies = $config['__construct()']; |
|||||||||
396 | 6 | unset($config['__construct()']); |
|||||||||
397 | } |
||||||||||
398 | 1588 | foreach ($params as $index => $param) { |
|||||||||
399 | 200 | $addDependencies[$index] = $param; |
|||||||||
400 | } |
||||||||||
401 | |||||||||||
402 | 1588 | $this->validateDependencies($addDependencies); |
|||||||||
403 | |||||||||||
404 | 1587 | if ($addDependencies && is_int(key($addDependencies))) { |
|||||||||
405 | 202 | $dependencies = array_values($dependencies); |
|||||||||
406 | 202 | $dependencies = $this->mergeDependencies($dependencies, $addDependencies); |
|||||||||
407 | } else { |
||||||||||
408 | 1585 | $dependencies = $this->mergeDependencies($dependencies, $addDependencies); |
|||||||||
409 | 1585 | $dependencies = array_values($dependencies); |
|||||||||
410 | } |
||||||||||
411 | |||||||||||
412 | 1587 | $dependencies = $this->resolveDependencies($dependencies, $reflection); |
|||||||||
413 | 1586 | if (!$reflection->isInstantiable()) { |
|||||||||
414 | 4 | throw new NotInstantiableException($reflection->name); |
|||||||||
415 | } |
||||||||||
416 | 1584 | if (empty($config)) { |
|||||||||
417 | 1262 | return $reflection->newInstanceArgs($dependencies); |
|||||||||
418 | } |
||||||||||
419 | |||||||||||
420 | 1405 | $config = $this->resolveDependencies($config); |
|||||||||
421 | |||||||||||
422 | 1405 | if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) { |
|||||||||
423 | // set $config as the last parameter (existing one will be overwritten) |
||||||||||
424 | 1404 | $dependencies[count($dependencies) - 1] = $config; |
|||||||||
425 | 1404 | return $reflection->newInstanceArgs($dependencies); |
|||||||||
426 | } |
||||||||||
427 | |||||||||||
428 | 1 | $object = $reflection->newInstanceArgs($dependencies); |
|||||||||
429 | 1 | foreach ($config as $name => $value) { |
|||||||||
430 | 1 | $object->$name = $value; |
|||||||||
431 | } |
||||||||||
432 | |||||||||||
433 | 1 | return $object; |
|||||||||
434 | } |
||||||||||
435 | |||||||||||
436 | /** |
||||||||||
437 | * @param array $a |
||||||||||
438 | * @param array $b |
||||||||||
439 | * @return array |
||||||||||
440 | */ |
||||||||||
441 | 1587 | private function mergeDependencies($a, $b) |
|||||||||
442 | { |
||||||||||
443 | 1587 | foreach ($b as $index => $dependency) { |
|||||||||
444 | 205 | $a[$index] = $dependency; |
|||||||||
445 | } |
||||||||||
446 | 1587 | return $a; |
|||||||||
447 | } |
||||||||||
448 | |||||||||||
449 | /** |
||||||||||
450 | * @param array $parameters |
||||||||||
451 | * @throws InvalidConfigException |
||||||||||
452 | */ |
||||||||||
453 | 1588 | private function validateDependencies($parameters) |
|||||||||
454 | { |
||||||||||
455 | 1588 | $hasStringParameter = false; |
|||||||||
456 | 1588 | $hasIntParameter = false; |
|||||||||
457 | 1588 | foreach ($parameters as $index => $parameter) { |
|||||||||
458 | 206 | if (is_string($index)) { |
|||||||||
459 | 4 | $hasStringParameter = true; |
|||||||||
460 | 4 | if ($hasIntParameter) { |
|||||||||
461 | 4 | break; |
|||||||||
462 | } |
||||||||||
463 | } else { |
||||||||||
464 | 203 | $hasIntParameter = true; |
|||||||||
465 | 203 | if ($hasStringParameter) { |
|||||||||
466 | 1 | break; |
|||||||||
467 | } |
||||||||||
468 | } |
||||||||||
469 | } |
||||||||||
470 | 1588 | if ($hasIntParameter && $hasStringParameter) { |
|||||||||
471 | 1 | throw new InvalidConfigException( |
|||||||||
472 | 1 | 'Dependencies indexed by name and by position in the same array are not allowed.' |
|||||||||
473 | 1 | ); |
|||||||||
474 | } |
||||||||||
475 | } |
||||||||||
476 | |||||||||||
477 | /** |
||||||||||
478 | * Merges the user-specified constructor parameters with the ones registered via [[set()]]. |
||||||||||
479 | * @param string $class class name, interface name or alias name |
||||||||||
480 | * @param array $params the constructor parameters |
||||||||||
481 | * @return array the merged parameters |
||||||||||
482 | */ |
||||||||||
483 | 30 | protected function mergeParams($class, $params) |
|||||||||
484 | { |
||||||||||
485 | 30 | if (empty($this->_params[$class])) { |
|||||||||
486 | 30 | return $params; |
|||||||||
487 | 3 | } elseif (empty($params)) { |
|||||||||
488 | 3 | return $this->_params[$class]; |
|||||||||
489 | } |
||||||||||
490 | |||||||||||
491 | $ps = $this->_params[$class]; |
||||||||||
492 | foreach ($params as $index => $value) { |
||||||||||
493 | $ps[$index] = $value; |
||||||||||
494 | } |
||||||||||
495 | |||||||||||
496 | return $ps; |
||||||||||
497 | } |
||||||||||
498 | |||||||||||
499 | /** |
||||||||||
500 | * Returns the dependencies of the specified class. |
||||||||||
501 | * @param string $class class name, interface name or alias name |
||||||||||
502 | * @return array the dependencies of the specified class. |
||||||||||
503 | * @throws NotInstantiableException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||||||
504 | */ |
||||||||||
505 | 1592 | protected function getDependencies($class) |
|||||||||
506 | { |
||||||||||
507 | 1592 | if (isset($this->_reflections[$class])) { |
|||||||||
508 | 1540 | return [$this->_reflections[$class], $this->_dependencies[$class]]; |
|||||||||
509 | } |
||||||||||
510 | |||||||||||
511 | 164 | $dependencies = []; |
|||||||||
512 | try { |
||||||||||
513 | 164 | $reflection = new ReflectionClass($class); |
|||||||||
514 | 8 | } catch (\ReflectionException $e) { |
|||||||||
515 | 8 | throw new NotInstantiableException( |
|||||||||
516 | 8 | $class, |
|||||||||
517 | 8 | 'Failed to instantiate component or class "' . $class . '".', |
|||||||||
518 | 8 | 0, |
|||||||||
519 | 8 | $e |
|||||||||
520 | 8 | ); |
|||||||||
521 | } |
||||||||||
522 | |||||||||||
523 | 160 | $constructor = $reflection->getConstructor(); |
|||||||||
524 | 160 | if ($constructor !== null) { |
|||||||||
525 | 157 | foreach ($constructor->getParameters() as $param) { |
|||||||||
526 | 157 | if (PHP_VERSION_ID >= 50600 && $param->isVariadic()) { |
|||||||||
527 | break; |
||||||||||
528 | } |
||||||||||
529 | |||||||||||
530 | 157 | if (PHP_VERSION_ID >= 80000) { |
|||||||||
531 | 157 | $c = $param->getType(); |
|||||||||
532 | 157 | $isClass = false; |
|||||||||
533 | 157 | if ($c instanceof ReflectionNamedType) { |
|||||||||
534 | 157 | $isClass = !$c->isBuiltin(); |
|||||||||
535 | } |
||||||||||
536 | } else { |
||||||||||
537 | try { |
||||||||||
538 | $c = $param->getClass(); |
||||||||||
539 | } catch (ReflectionException $e) { |
||||||||||
540 | if (!$this->isNulledParam($param)) { |
||||||||||
541 | $notInstantiableClass = null; |
||||||||||
542 | if (PHP_VERSION_ID >= 70000) { |
||||||||||
543 | $type = $param->getType(); |
||||||||||
544 | if ($type instanceof ReflectionNamedType) { |
||||||||||
545 | $notInstantiableClass = $type->getName(); |
||||||||||
546 | } |
||||||||||
547 | } |
||||||||||
548 | throw new NotInstantiableException( |
||||||||||
549 | $notInstantiableClass, |
||||||||||
550 | $notInstantiableClass === null ? 'Can not instantiate unknown class.' : null |
||||||||||
551 | ); |
||||||||||
552 | } else { |
||||||||||
553 | $c = null; |
||||||||||
554 | } |
||||||||||
555 | } |
||||||||||
556 | $isClass = $c !== null; |
||||||||||
557 | } |
||||||||||
558 | 157 | $className = $isClass ? $c->getName() : null; |
|||||||||
559 | |||||||||||
560 | 157 | if ($className !== null) { |
|||||||||
561 | 9 | $dependencies[$param->getName()] = Instance::of($className, $this->isNulledParam($param)); |
|||||||||
562 | } else { |
||||||||||
563 | 155 | $dependencies[$param->getName()] = $param->isDefaultValueAvailable() |
|||||||||
564 | 150 | ? $param->getDefaultValue() |
|||||||||
565 | 27 | : null; |
|||||||||
566 | } |
||||||||||
567 | } |
||||||||||
568 | } |
||||||||||
569 | |||||||||||
570 | 160 | $this->_reflections[$class] = $reflection; |
|||||||||
571 | 160 | $this->_dependencies[$class] = $dependencies; |
|||||||||
572 | |||||||||||
573 | 160 | return [$reflection, $dependencies]; |
|||||||||
574 | } |
||||||||||
575 | |||||||||||
576 | /** |
||||||||||
577 | * @param ReflectionParameter $param |
||||||||||
578 | * @return bool |
||||||||||
579 | */ |
||||||||||
580 | 9 | private function isNulledParam($param) |
|||||||||
581 | { |
||||||||||
582 | 9 | return $param->isOptional() || (PHP_VERSION_ID >= 70100 && $param->getType()->allowsNull()); |
|||||||||
583 | } |
||||||||||
584 | |||||||||||
585 | /** |
||||||||||
586 | * Resolves dependencies by replacing them with the actual object instances. |
||||||||||
587 | * @param array $dependencies the dependencies |
||||||||||
588 | * @param ReflectionClass|null $reflection the class reflection associated with the dependencies |
||||||||||
589 | * @return array the resolved dependencies |
||||||||||
590 | * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||||||
591 | */ |
||||||||||
592 | 1589 | protected function resolveDependencies($dependencies, $reflection = null) |
|||||||||
593 | { |
||||||||||
594 | 1589 | foreach ($dependencies as $index => $dependency) { |
|||||||||
595 | 1584 | if ($dependency instanceof Instance) { |
|||||||||
596 | 9 | if ($dependency->id !== null) { |
|||||||||
597 | 9 | $dependencies[$index] = $dependency->get($this); |
|||||||||
598 | } elseif ($reflection !== null) { |
||||||||||
599 | $name = $reflection->getConstructor()->getParameters()[$index]->getName(); |
||||||||||
600 | $class = $reflection->getName(); |
||||||||||
601 | 7 | throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\"."); |
|||||||||
602 | } |
||||||||||
603 | 1582 | } elseif ($this->_resolveArrays && is_array($dependency)) { |
|||||||||
604 | 1 | $dependencies[$index] = $this->resolveDependencies($dependency, $reflection); |
|||||||||
605 | } |
||||||||||
606 | } |
||||||||||
607 | |||||||||||
608 | 1588 | return $dependencies; |
|||||||||
609 | } |
||||||||||
610 | |||||||||||
611 | /** |
||||||||||
612 | * Invoke a callback with resolving dependencies in parameters. |
||||||||||
613 | * |
||||||||||
614 | * This method allows invoking a callback and let type hinted parameter names to be |
||||||||||
615 | * resolved as objects of the Container. It additionally allows calling function using named parameters. |
||||||||||
616 | * |
||||||||||
617 | * For example, the following callback may be invoked using the Container to resolve the formatter dependency: |
||||||||||
618 | * |
||||||||||
619 | * ```php |
||||||||||
620 | * $formatString = function($string, \yii\i18n\Formatter $formatter) { |
||||||||||
621 | * // ... |
||||||||||
622 | * } |
||||||||||
623 | * Yii::$container->invoke($formatString, ['string' => 'Hello World!']); |
||||||||||
624 | * ``` |
||||||||||
625 | * |
||||||||||
626 | * This will pass the string `'Hello World!'` as the first param, and a formatter instance created |
||||||||||
627 | * by the DI container as the second param to the callable. |
||||||||||
628 | * |
||||||||||
629 | * @param callable $callback callable to be invoked. |
||||||||||
630 | * @param array $params The array of parameters for the function. |
||||||||||
631 | * This can be either a list of parameters, or an associative array representing named function parameters. |
||||||||||
632 | * @return mixed the callback return value. |
||||||||||
633 | * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||||||
634 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||||||
635 | * @since 2.0.7 |
||||||||||
636 | */ |
||||||||||
637 | 6 | public function invoke(callable $callback, $params = []) |
|||||||||
638 | { |
||||||||||
639 | 6 | return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params)); |
|||||||||
640 | } |
||||||||||
641 | |||||||||||
642 | /** |
||||||||||
643 | * Resolve dependencies for a function. |
||||||||||
644 | * |
||||||||||
645 | * This method can be used to implement similar functionality as provided by [[invoke()]] in other |
||||||||||
646 | * components. |
||||||||||
647 | * |
||||||||||
648 | * @param callable $callback callable to be invoked. |
||||||||||
649 | * @param array $params The array of parameters for the function, can be either numeric or associative. |
||||||||||
650 | * @return array The resolved dependencies. |
||||||||||
651 | * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. |
||||||||||
652 | * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9) |
||||||||||
653 | * @since 2.0.7 |
||||||||||
654 | */ |
||||||||||
655 | 9 | public function resolveCallableDependencies(callable $callback, $params = []) |
|||||||||
656 | { |
||||||||||
657 | 9 | if (is_array($callback)) { |
|||||||||
658 | 4 | $reflection = new \ReflectionMethod($callback[0], $callback[1]); |
|||||||||
659 | 7 | } elseif (is_object($callback) && !$callback instanceof \Closure) { |
|||||||||
660 | 1 | $reflection = new \ReflectionMethod($callback, '__invoke'); |
|||||||||
661 | } else { |
||||||||||
662 | 7 | $reflection = new \ReflectionFunction($callback); |
|||||||||
663 | } |
||||||||||
664 | |||||||||||
665 | 9 | $args = []; |
|||||||||
666 | |||||||||||
667 | 9 | $associative = ArrayHelper::isAssociative($params); |
|||||||||
668 | |||||||||||
669 | 9 | foreach ($reflection->getParameters() as $param) { |
|||||||||
670 | 7 | $name = $param->getName(); |
|||||||||
671 | |||||||||||
672 | 7 | if (PHP_VERSION_ID >= 80000) { |
|||||||||
673 | 7 | $class = $param->getType(); |
|||||||||
674 | 7 | 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 ![]() |
|||||||||||
675 | 2 | $isClass = false; |
|||||||||
676 | 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
![]() |
|||||||||||
677 | 2 | if (!$type->isBuiltin()) { |
|||||||||
678 | 2 | $class = $type; |
|||||||||
679 | 2 | $isClass = true; |
|||||||||
680 | 2 | break; |
|||||||||
681 | } |
||||||||||
682 | } |
||||||||||
683 | } else { |
||||||||||
684 | 7 | $isClass = $class !== null && !$class->isBuiltin(); |
|||||||||
685 | } |
||||||||||
686 | } else { |
||||||||||
687 | $class = $param->getClass(); |
||||||||||
688 | $isClass = $class !== null; |
||||||||||
689 | } |
||||||||||
690 | |||||||||||
691 | 7 | if ($isClass) { |
|||||||||
692 | 5 | $className = $class->getName(); |
|||||||||
0 ignored issues
–
show
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. ![]() 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
![]() |
|||||||||||
693 | 5 | if (PHP_VERSION_ID >= 50600 && $param->isVariadic()) { |
|||||||||
694 | $args = array_merge($args, array_values($params)); |
||||||||||
695 | break; |
||||||||||
696 | } |
||||||||||
697 | |||||||||||
698 | 5 | if ($associative && isset($params[$name]) && $params[$name] instanceof $className) { |
|||||||||
699 | $args[] = $params[$name]; |
||||||||||
700 | unset($params[$name]); |
||||||||||
701 | 5 | } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) { |
|||||||||
702 | 1 | $args[] = array_shift($params); |
|||||||||
703 | 5 | } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) { |
|||||||||
704 | 1 | $args[] = $obj; |
|||||||||
705 | } else { |
||||||||||
706 | // If the argument is optional we catch not instantiable exceptions |
||||||||||
707 | try { |
||||||||||
708 | 5 | $args[] = $this->get($className); |
|||||||||
709 | 1 | } catch (NotInstantiableException $e) { |
|||||||||
710 | 1 | if ($param->isDefaultValueAvailable()) { |
|||||||||
711 | 1 | $args[] = $param->getDefaultValue(); |
|||||||||
712 | } else { |
||||||||||
713 | 5 | throw $e; |
|||||||||
714 | } |
||||||||||
715 | } |
||||||||||
716 | } |
||||||||||
717 | 4 | } elseif ($associative && isset($params[$name])) { |
|||||||||
718 | 2 | $args[] = $params[$name]; |
|||||||||
719 | 2 | unset($params[$name]); |
|||||||||
720 | 4 | } elseif (!$associative && count($params)) { |
|||||||||
721 | 3 | $args[] = array_shift($params); |
|||||||||
722 | 3 | } elseif ($param->isDefaultValueAvailable()) { |
|||||||||
723 | 3 | $args[] = $param->getDefaultValue(); |
|||||||||
724 | } elseif (!$param->isOptional()) { |
||||||||||
725 | $funcName = $reflection->getName(); |
||||||||||
726 | throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\"."); |
||||||||||
727 | } |
||||||||||
728 | } |
||||||||||
729 | |||||||||||
730 | 9 | foreach ($params as $value) { |
|||||||||
731 | $args[] = $value; |
||||||||||
732 | } |
||||||||||
733 | |||||||||||
734 | 9 | return $args; |
|||||||||
735 | } |
||||||||||
736 | |||||||||||
737 | /** |
||||||||||
738 | * Registers class definitions within this container. |
||||||||||
739 | * |
||||||||||
740 | * @param array $definitions array of definitions. There are two allowed formats of array. |
||||||||||
741 | * The first format: |
||||||||||
742 | * - key: class name, interface name or alias name. The key will be passed to the [[set()]] method |
||||||||||
743 | * as a first argument `$class`. |
||||||||||
744 | * - value: the definition associated with `$class`. Possible values are described in |
||||||||||
745 | * [[set()]] documentation for the `$definition` parameter. Will be passed to the [[set()]] method |
||||||||||
746 | * as the second argument `$definition`. |
||||||||||
747 | * |
||||||||||
748 | * Example: |
||||||||||
749 | * ```php |
||||||||||
750 | * $container->setDefinitions([ |
||||||||||
751 | * 'yii\web\Request' => 'app\components\Request', |
||||||||||
752 | * 'yii\web\Response' => [ |
||||||||||
753 | * 'class' => 'app\components\Response', |
||||||||||
754 | * 'format' => 'json' |
||||||||||
755 | * ], |
||||||||||
756 | * 'foo\Bar' => function () { |
||||||||||
757 | * $qux = new Qux; |
||||||||||
758 | * $foo = new Foo($qux); |
||||||||||
759 | * return new Bar($foo); |
||||||||||
760 | * } |
||||||||||
761 | * ]); |
||||||||||
762 | * ``` |
||||||||||
763 | * |
||||||||||
764 | * The second format: |
||||||||||
765 | * - key: class name, interface name or alias name. The key will be passed to the [[set()]] method |
||||||||||
766 | * as a first argument `$class`. |
||||||||||
767 | * - value: array of two elements. The first element will be passed the [[set()]] method as the |
||||||||||
768 | * second argument `$definition`, the second one — as `$params`. |
||||||||||
769 | * |
||||||||||
770 | * Example: |
||||||||||
771 | * ```php |
||||||||||
772 | * $container->setDefinitions([ |
||||||||||
773 | * 'foo\Bar' => [ |
||||||||||
774 | * ['class' => 'app\Bar'], |
||||||||||
775 | * [Instance::of('baz')] |
||||||||||
776 | * ] |
||||||||||
777 | * ]); |
||||||||||
778 | * ``` |
||||||||||
779 | * |
||||||||||
780 | * @see set() to know more about possible values of definitions |
||||||||||
781 | * @since 2.0.11 |
||||||||||
782 | */ |
||||||||||
783 | 10 | public function setDefinitions(array $definitions) |
|||||||||
784 | { |
||||||||||
785 | 10 | foreach ($definitions as $class => $definition) { |
|||||||||
786 | 10 | if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition && is_array($definition[1])) { |
|||||||||
787 | 1 | $this->set($class, $definition[0], $definition[1]); |
|||||||||
788 | 1 | continue; |
|||||||||
789 | } |
||||||||||
790 | |||||||||||
791 | 10 | $this->set($class, $definition); |
|||||||||
792 | } |
||||||||||
793 | } |
||||||||||
794 | |||||||||||
795 | /** |
||||||||||
796 | * Registers class definitions as singletons within this container by calling [[setSingleton()]]. |
||||||||||
797 | * |
||||||||||
798 | * @param array $singletons array of singleton definitions. See [[setDefinitions()]] |
||||||||||
799 | * for allowed formats of array. |
||||||||||
800 | * |
||||||||||
801 | * @see setDefinitions() for allowed formats of $singletons parameter |
||||||||||
802 | * @see setSingleton() to know more about possible values of definitions |
||||||||||
803 | * @since 2.0.11 |
||||||||||
804 | */ |
||||||||||
805 | 4 | public function setSingletons(array $singletons) |
|||||||||
806 | { |
||||||||||
807 | 4 | foreach ($singletons as $class => $definition) { |
|||||||||
808 | 4 | if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition) { |
|||||||||
809 | 1 | $this->setSingleton($class, $definition[0], $definition[1]); |
|||||||||
810 | 1 | continue; |
|||||||||
811 | } |
||||||||||
812 | |||||||||||
813 | 4 | $this->setSingleton($class, $definition); |
|||||||||
814 | } |
||||||||||
815 | } |
||||||||||
816 | |||||||||||
817 | /** |
||||||||||
818 | * @param bool $value whether to attempt to resolve elements in array dependencies |
||||||||||
819 | * @since 2.0.37 |
||||||||||
820 | */ |
||||||||||
821 | 1 | public function setResolveArrays($value) |
|||||||||
822 | { |
||||||||||
823 | 1 | $this->_resolveArrays = (bool) $value; |
|||||||||
824 | } |
||||||||||
825 | } |
||||||||||
826 |