Test Failed
Pull Request — 22.0 (#20450)
by Alexander
17:23 queued 11:14
created

Container::getDependencies()   B

Complexity

Conditions 10
Paths 5

Size

Total Lines 48
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 15.5918

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 29
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 48
ccs 21
cts 34
cp 0.6176
crap 15.5918
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
The expression return $definition also could return the type callable which is incompatible with the documented return type array.
Loading history...
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 ($param->isVariadic()) {
527
                    break;
528
                }
529
530 157
                $c = $param->getType();
531 157
                $isClass = false;
532 157
533 157
                if ($c instanceof ReflectionNamedType) {
534 157
                    $isClass = !$c->isBuiltin();
535
                }
536
537
                $className = $isClass ? $c->getName() : null;
538
539
                if ($className !== null) {
540
                    $dependencies[$param->getName()] = Instance::of($className, $this->isNulledParam($param));
541
                } else {
542
                    $dependencies[$param->getName()] = $param->isDefaultValueAvailable()
543
                        ? $param->getDefaultValue()
544
                        : null;
545
                }
546
            }
547
        }
548
549
        $this->_reflections[$class] = $reflection;
550
        $this->_dependencies[$class] = $dependencies;
551
552
        return [$reflection, $dependencies];
553
    }
554
555
    /**
556
     * @param ReflectionParameter $param
557
     * @return bool
558 157
     */
559
    private function isNulledParam($param)
560 157
    {
561 9
        return $param->isOptional() || $param->getType()->allowsNull();
562
    }
563 155
564 150
    /**
565 27
     * Resolves dependencies by replacing them with the actual object instances.
566
     * @param array $dependencies the dependencies
567
     * @param ReflectionClass|null $reflection the class reflection associated with the dependencies
568
     * @return array the resolved dependencies
569
     * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
570 160
     */
571 160
    protected function resolveDependencies($dependencies, $reflection = null)
572
    {
573 160
        foreach ($dependencies as $index => $dependency) {
574
            if ($dependency instanceof Instance) {
575
                if ($dependency->id !== null) {
576
                    $dependencies[$index] = $dependency->get($this);
577
                } elseif ($reflection !== null) {
578
                    $name = $reflection->getConstructor()->getParameters()[$index]->getName();
579
                    $class = $reflection->getName();
580 9
                    throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
581
                }
582 9
            } elseif ($this->_resolveArrays && is_array($dependency)) {
583
                $dependencies[$index] = $this->resolveDependencies($dependency, $reflection);
584
            }
585
        }
586
587
        return $dependencies;
588
    }
589
590
    /**
591
     * Invoke a callback with resolving dependencies in parameters.
592 1589
     *
593
     * This method allows invoking a callback and let type hinted parameter names to be
594 1589
     * resolved as objects of the Container. It additionally allows calling function using named parameters.
595 1584
     *
596 9
     * For example, the following callback may be invoked using the Container to resolve the formatter dependency:
597 9
     *
598
     * ```php
599
     * $formatString = function($string, \yii\i18n\Formatter $formatter) {
600
     *    // ...
601 7
     * }
602
     * Yii::$container->invoke($formatString, ['string' => 'Hello World!']);
603 1582
     * ```
604 1
     *
605
     * This will pass the string `'Hello World!'` as the first param, and a formatter instance created
606
     * by the DI container as the second param to the callable.
607
     *
608 1588
     * @param callable $callback callable to be invoked.
609
     * @param array $params The array of parameters for the function.
610
     * This can be either a list of parameters, or an associative array representing named function parameters.
611
     * @return mixed the callback return value.
612
     * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
613
     * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9)
614
     * @since 2.0.7
615
     */
616
    public function invoke(callable $callback, $params = [])
617
    {
618
        return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params));
619
    }
620
621
    /**
622
     * Resolve dependencies for a function.
623
     *
624
     * This method can be used to implement similar functionality as provided by [[invoke()]] in other
625
     * components.
626
     *
627
     * @param callable $callback callable to be invoked.
628
     * @param array $params The array of parameters for the function, can be either numeric or associative.
629
     * @return array The resolved dependencies.
630
     * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
631
     * @throws NotInstantiableException If resolved to an abstract class or an interface (since 2.0.9)
632
     * @since 2.0.7
633
     */
634
    public function resolveCallableDependencies(callable $callback, $params = [])
635
    {
636
        if (is_array($callback)) {
637 6
            $reflection = new \ReflectionMethod($callback[0], $callback[1]);
638
        } elseif (is_object($callback) && !$callback instanceof \Closure) {
639 6
            $reflection = new \ReflectionMethod($callback, '__invoke');
640
        } else {
641
            $reflection = new \ReflectionFunction($callback);
642
        }
643
644
        $args = [];
645
646
        $associative = ArrayHelper::isAssociative($params);
647
648
        foreach ($reflection->getParameters() as $param) {
649
            $name = $param->getName();
650
651
            $class = $param->getType();
652
653
            if ($class instanceof \ReflectionUnionType || $class instanceof \ReflectionIntersectionType) {
0 ignored issues
show
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
654
                $isClass = false;
655 9
                foreach ($class->getTypes() as $type) {
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

655
                foreach ($class->/** @scrutinizer ignore-call */ getTypes() as $type) {
Loading history...
656
                    if (!$type->isBuiltin()) {
657 9
                        $class = $type;
658 4
                        $isClass = true;
659 7
                        break;
660 1
                    }
661
                }
662 7
            } else {
663
                $isClass = $class !== null && !$class->isBuiltin();
664
            }
665 9
666
            if ($isClass) {
667 9
                $className = $class->getName();
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

667
                /** @scrutinizer ignore-call */ 
668
                $className = $class->getName();

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...
Bug introduced by
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 ignore-call  annotation

667
                /** @scrutinizer ignore-call */ 
668
                $className = $class->getName();
Loading history...
668
669 9
                if ($param->isVariadic()) {
670 7
                    $args = array_merge($args, array_values($params));
671
                    break;
672 7
                }
673 7
674 7
                if ($associative && isset($params[$name]) && $params[$name] instanceof $className) {
675 2
                    $args[] = $params[$name];
676 2
                    unset($params[$name]);
677 2
                } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) {
678 2
                    $args[] = array_shift($params);
679 2
                } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) {
680 2
                    $args[] = $obj;
681
                } else {
682
                    // If the argument is optional we catch not instantiable exceptions
683
                    try {
684 7
                        $args[] = $this->get($className);
685
                    } catch (NotInstantiableException $e) {
686
                        if ($param->isDefaultValueAvailable()) {
687
                            $args[] = $param->getDefaultValue();
688
                        } else {
689
                            throw $e;
690
                        }
691 7
                    }
692 5
                }
693 5
            } elseif ($associative && isset($params[$name])) {
694
                $args[] = $params[$name];
695
                unset($params[$name]);
696
            } elseif (!$associative && count($params)) {
697
                $args[] = array_shift($params);
698 5
            } elseif ($param->isDefaultValueAvailable()) {
699
                $args[] = $param->getDefaultValue();
700
            } elseif (!$param->isOptional()) {
701 5
                $funcName = $reflection->getName();
702 1
                throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\".");
703 5
            }
704 1
        }
705
706
        foreach ($params as $value) {
707
            $args[] = $value;
708 5
        }
709 1
710 1
        return $args;
711 1
    }
712
713 5
    /**
714
     * Registers class definitions within this container.
715
     *
716
     * @param array $definitions array of definitions. There are two allowed formats of array.
717 4
     * The first format:
718 2
     *  - key: class name, interface name or alias name. The key will be passed to the [[set()]] method
719 2
     *    as a first argument `$class`.
720 4
     *  - value: the definition associated with `$class`. Possible values are described in
721 3
     *    [[set()]] documentation for the `$definition` parameter. Will be passed to the [[set()]] method
722 3
     *    as the second argument `$definition`.
723 3
     *
724
     * Example:
725
     * ```php
726
     * $container->setDefinitions([
727
     *     'yii\web\Request' => 'app\components\Request',
728
     *     'yii\web\Response' => [
729
     *         'class' => 'app\components\Response',
730 9
     *         'format' => 'json'
731
     *     ],
732
     *     'foo\Bar' => function () {
733
     *         $qux = new Qux;
734 9
     *         $foo = new Foo($qux);
735
     *         return new Bar($foo);
736
     *     }
737
     * ]);
738
     * ```
739
     *
740
     * The second format:
741
     *  - key: class name, interface name or alias name. The key will be passed to the [[set()]] method
742
     *    as a first argument `$class`.
743
     *  - value: array of two elements. The first element will be passed the [[set()]] method as the
744
     *    second argument `$definition`, the second one — as `$params`.
745
     *
746
     * Example:
747
     * ```php
748
     * $container->setDefinitions([
749
     *     'foo\Bar' => [
750
     *          ['class' => 'app\Bar'],
751
     *          [Instance::of('baz')]
752
     *      ]
753
     * ]);
754
     * ```
755
     *
756
     * @see set() to know more about possible values of definitions
757
     * @since 2.0.11
758
     */
759
    public function setDefinitions(array $definitions)
760
    {
761
        foreach ($definitions as $class => $definition) {
762
            if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition && is_array($definition[1])) {
763
                $this->set($class, $definition[0], $definition[1]);
764
                continue;
765
            }
766
767
            $this->set($class, $definition);
768
        }
769
    }
770
771
    /**
772
     * Registers class definitions as singletons within this container by calling [[setSingleton()]].
773
     *
774
     * @param array $singletons array of singleton definitions. See [[setDefinitions()]]
775
     * for allowed formats of array.
776
     *
777
     * @see setDefinitions() for allowed formats of $singletons parameter
778
     * @see setSingleton() to know more about possible values of definitions
779
     * @since 2.0.11
780
     */
781
    public function setSingletons(array $singletons)
782
    {
783 10
        foreach ($singletons as $class => $definition) {
784
            if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition) {
785 10
                $this->setSingleton($class, $definition[0], $definition[1]);
786 10
                continue;
787 1
            }
788 1
789
            $this->setSingleton($class, $definition);
790
        }
791 10
    }
792
793
    /**
794
     * @param bool $value whether to attempt to resolve elements in array dependencies
795
     * @since 2.0.37
796
     */
797
    public function setResolveArrays($value)
798
    {
799
        $this->_resolveArrays = (bool) $value;
800
    }
801
}
802