Passed
Pull Request — master (#3)
by Alexander
14:33
created

Injector::resolveCallableDependencies()   F

Complexity

Conditions 24
Paths 1740

Size

Total Lines 89
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 35.2369

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 19
cts 26
cp 0.7308
crap 35.2369
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
class Injector
17 3
{
18
    private ContainerInterface $container;
19 3
20
    public function __construct(ContainerInterface $container)
21
    {
22
        $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 $params 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 3
     * @throws ContainerExceptionInterface if a dependency cannot be resolved or if a dependency cannot be fulfilled.
49
     * @throws ReflectionException
50 3
     */
51
    public function invoke(callable $callback, array $params = [])
52
    {
53
        return \call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params));
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 3
     * @throws ContainerExceptionInterface if a dependency cannot be resolved or if a dependency cannot be fulfilled.
67
     * @throws ReflectionException
68 3
     */
69
    private function resolveCallableDependencies(callable $callback, array $parameters = []): array
70
    {
71
        if (\is_object($callback) && !$callback instanceof \Closure) {
72 3
            $callback = [$callback, '__invoke'];
73
        }
74
75 3
        if (\is_array($callback)) {
76
            $reflection = new \ReflectionMethod($callback[0], $callback[1]);
77
        } else {
78 3
            $reflection = new \ReflectionFunction($callback);
79
        }
80 3
81 3
        $arguments = [];
82 3
83 3
        $pushUnusedParams = true;
84 3
        foreach ($reflection->getParameters() as $param) {
85
            $name = $param->getName();
86
            $class = $param->getClass();
87
            $hasType = $param->hasType();
88
            $isNullable = $param->allowsNull() && $hasType;
89 3
            $isVariadic = $param->isVariadic();
90 1
            $error = null;
91 1
92
            // Get argument by name
93
            if (array_key_exists($name, $parameters)) {
94 3
                if ($isVariadic && is_array($parameters[$name])) {
95
                    $arguments = array_merge($arguments, array_values($parameters[$name]));
96
                } else {
97
                    $arguments[] = $parameters[$name];
98 1
                }
99
                unset($parameters[$name]);
100 1
                continue;
101
            }
102 1
103 1
            if ($class !== null) {
104 1
                // Unnamed parameters
105
                $className = $class->getName();
106
                $found = false;
107
                foreach ($parameters as $key => $item) {
108 1
                    if (!is_int($key)) {
109
                        continue;
110
                    }
111
                    if ($item instanceof $className) {
112 1
                        $found = true;
113
                        $arguments[] = $item;
114
                        unset($parameters[$key]);
115
                        if (!$isVariadic) {
116
                            break;
117
                        }
118
                    }
119
                }
120
                if ($found) {
121
                    $pushUnusedParams = false;
122
                    continue;
123
                }
124
125
                // If the argument is optional we catch not instantiable exceptions
126
                try {
127
                    $arguments[] = $this->container->get($className);
128
                    continue;
129
                } catch (NotFoundExceptionInterface $e) {
130
                    $error = $e;
131
                }
132
            }
133
134
            if ($param->isDefaultValueAvailable()) {
135
                $arguments[] = $param->getDefaultValue();
136
            } elseif (!$param->isOptional()) {
137
                if ($isNullable) {
138
                    $arguments[] = null;
139
                } else {
140
                    throw $error ?? new MissingRequiredArgumentException($name, $reflection->getName());
141
                }
142
            } elseif ($hasType) {
143
                $pushUnusedParams = false;
144
            }
145
        }
146
147
        foreach ($parameters as $key => $value) {
148
            if (is_int($key)) {
149
                if (!is_object($value)) {
150
                    throw new InvalidParameterException((string)$key, $reflection->getName());
151
                }
152
                if ($pushUnusedParams) {
153
                    $arguments[] = $value;
154
                }
155
            }
156
        }
157
        return $arguments;
158
    }
159
}
160