Passed
Pull Request — master (#3)
by Alexander
14:16 queued 12:44
created

Injector::resolveCallableDependencies()   F

Complexity

Conditions 24
Paths 1740

Size

Total Lines 89
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 24.0034

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 24
eloc 58
c 4
b 0
f 0
nc 1740
nop 2
dl 0
loc 89
ccs 54
cts 55
cp 0.9818
crap 24.0034
rs 0

How to fix   Long Method    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
declare(strict_types=1);
4
5
namespace Yiisoft\Injector;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use Psr\Container\NotFoundExceptionInterface;
10
use ReflectionException;
11
12
/**
13
 * Injector is able to analyze callable dependencies based on
14
 * type hinting and inject them from any PSR-11 compatible container.
15
 */
16
final class Injector
17
{
18
    private ContainerInterface $container;
19
20 27
    public function __construct(ContainerInterface $container)
21
    {
22 27
        $this->container = $container;
23
    }
24
25
    /**
26
     * Invoke a callback with resolving dependencies in parameters.
27
     *
28
     * This methods allows invoking a callback and let type hinted parameter names to be
29
     * resolved as objects of the Container. It additionally allow calling function using named parameters.
30
     *
31
     * For example, the following callback may be invoked using the Container to resolve the formatter dependency:
32
     *
33
     * ```php
34
     * $formatString = function($string, \yii\i18n\Formatter $formatter) {
35
     *    // ...
36
     * }
37
     * $container->invoke($formatString, ['string' => 'Hello World!']);
38
     * ```
39
     *
40
     * This will pass the string `'Hello World!'` as the first param, and a formatter instance created
41
     * by the DI container as the second param to the callable.
42
     *
43
     * @param callable $callback callable to be invoked.
44
     * @param array $parameters The array of parameters for the function.
45
     * This can be either a list of parameters, or an associative array representing named function parameters.
46
     * @return mixed the callback return value.
47
     * @throws MissingRequiredArgumentException  if required argument is missing.
48
     * @throws ContainerExceptionInterface if a dependency cannot be resolved or if a dependency cannot be fulfilled.
49
     * @throws ReflectionException
50
     */
51 27
    public function invoke(callable $callback, array $parameters = [])
52
    {
53 27
        return \call_user_func_array($callback, $this->resolveCallableDependencies($callback, $parameters));
54
    }
55
56
    /**
57
     * Resolve dependencies for a function.
58
     *
59
     * This method can be used to implement similar functionality as provided by [[invoke()]] in other
60
     * components.
61
     *
62
     * @param callable $callback callable to be invoked.
63
     * @param array $parameters The array of parameters for the function, can be either numeric or associative.
64
     * @return array The resolved dependencies.
65
     * @throws MissingRequiredArgumentException if required argument is missing.
66
     * @throws ContainerExceptionInterface if a dependency cannot be resolved or if a dependency cannot be fulfilled.
67
     * @throws ReflectionException
68
     */
69 27
    private function resolveCallableDependencies(callable $callback, array $parameters = []): array
70
    {
71 27
        if (\is_object($callback) && !$callback instanceof \Closure) {
72
            $callback = [$callback, '__invoke'];
73
        }
74
75 27
        if (\is_array($callback)) {
76 2
            $reflection = new \ReflectionMethod($callback[0], $callback[1]);
77
        } else {
78 25
            $reflection = new \ReflectionFunction($callback);
79
        }
80
81 27
        $arguments = [];
82
83 27
        $pushUnusedParams = true;
84 27
        foreach ($reflection->getParameters() as $param) {
85 24
            $name = $param->getName();
86 24
            $class = $param->getClass();
87 24
            $hasType = $param->hasType();
88 24
            $isNullable = $param->allowsNull() && $hasType;
89 24
            $isVariadic = $param->isVariadic();
90 24
            $error = null;
91
92
            // Get argument by name
93 24
            if (array_key_exists($name, $parameters)) {
94 4
                if ($isVariadic && is_array($parameters[$name])) {
95 1
                    $arguments = array_merge($arguments, array_values($parameters[$name]));
96
                } else {
97 3
                    $arguments[] = $parameters[$name];
98
                }
99 4
                unset($parameters[$name]);
100 4
                continue;
101
            }
102
103 21
            if ($class !== null) {
104
                // Unnamed parameters
105 14
                $className = $class->getName();
106 14
                $found = false;
107 14
                foreach ($parameters as $key => $item) {
108 6
                    if (!is_int($key)) {
109 1
                        continue;
110
                    }
111 5
                    if ($item instanceof $className) {
112 4
                        $found = true;
113 4
                        $arguments[] = $item;
114 4
                        unset($parameters[$key]);
115 4
                        if (!$isVariadic) {
116 3
                            break;
117
                        }
118
                    }
119
                }
120 14
                if ($found) {
121 4
                    $pushUnusedParams = false;
122 4
                    continue;
123
                }
124
125
                // If the argument is optional we catch not instantiable exceptions
126
                try {
127 13
                    $arguments[] = $this->container->get($className);
128 11
                    continue;
129 3
                } catch (NotFoundExceptionInterface $e) {
130 3
                    $error = $e;
131
                }
132
            }
133
134 13
            if ($param->isDefaultValueAvailable()) {
135 2
                $arguments[] = $param->getDefaultValue();
136 11
            } elseif (!$param->isOptional()) {
137 8
                if ($isNullable) {
138 2
                    $arguments[] = null;
139
                } else {
140 8
                    throw $error ?? new MissingRequiredArgumentException($name, $reflection->getName());
141
                }
142 3
            } elseif ($hasType) {
143 2
                $pushUnusedParams = false;
144
            }
145
        }
146
147 21
        foreach ($parameters as $key => $value) {
148 7
            if (is_int($key)) {
149 7
                if (!is_object($value)) {
150 2
                    throw new InvalidParameterException((string)$key, $reflection->getName());
151
                }
152 5
                if ($pushUnusedParams) {
153 2
                    $arguments[] = $value;
154
                }
155
            }
156
        }
157 19
        return $arguments;
158
    }
159
}
160