AutowireHelper   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Test Coverage

Coverage 90.53%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 44
eloc 88
c 3
b 0
f 0
dl 0
loc 194
ccs 86
cts 95
cp 0.9053
rs 8.8798

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A resolveStringAsObject() 0 7 2
A throwAutowireException() 0 15 5
D autowire() 0 77 23
B resolveArgumentsFromReflectionParametersObject() 0 43 7
A resolveArguments() 0 21 6

How to fix   Complexity   

Complex Class

Complex classes like AutowireHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AutowireHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 *  This file is part of the Micro framework package.
5
 *
6
 *  (c) Stanislau Komar <[email protected]>
7
 *
8
 *  For the full copyright and license information, please view the LICENSE
9
 *  file that was distributed with this source code.
10
 */
11
12
namespace Micro\Component\DependencyInjection\Autowire;
13
14
use Micro\Component\DependencyInjection\Autowire\Exception\AutowireException;
15
use Psr\Container\ContainerExceptionInterface;
16
use Psr\Container\ContainerInterface;
17
use Psr\Container\NotFoundExceptionInterface;
18
19
class AutowireHelper implements AutowireHelperInterface
20
{
21 19
    public function __construct(private ContainerInterface $container)
22
    {
23 19
    }
24
25 19
    public function autowire(string|array|callable $target): callable
26
    {
27 19
        return function () use ($target) {
28 19
            if (\is_array($target)) {
0 ignored issues
show
introduced by
The condition is_array($target) is always true.
Loading history...
29 11
                $target = array_filter($target); // Don't allow [class-name::class-name] method
30 11
                $tc = \count($target);
31 11
                if (0 === $tc || $tc > 2) {
32 2
                    $this->throwAutowireException($target, '');
33
                }
34
            }
35
36
            try {
37 17
                if ($target instanceof \Closure) {
0 ignored issues
show
introduced by
$target is never a sub-type of Closure.
Loading history...
38 8
                    $arguments = $this->resolveArguments($target);
39
40 6
                    return \call_user_func($target, ...$arguments);
41
                }
42
43 12
                if (\is_string($target) && class_exists($target)) {
0 ignored issues
show
introduced by
The condition is_string($target) is always false.
Loading history...
44 1
                    $arguments = $this->resolveArguments($target);
45
46 1
                    return new $target(...$arguments);
47
                }
48
49 11
                if (\is_object($target) && \is_callable($target)) {
0 ignored issues
show
introduced by
The condition is_object($target) is always false.
Loading history...
50
                    $arguments = $this->resolveArguments([$target, '__invoke']);
51
52
                    return \call_user_func($target, ...$arguments);
53
                }
54
55 11
                if (!\is_array($target)) {
0 ignored issues
show
introduced by
The condition is_array($target) is always true.
Loading history...
56 2
                    $this->throwAutowireException($target, '');
57
                }
58
59 9
                $object = $target[0] ?? null;
60 9
                $method = $target[1] ?? null;
61 9
                $arguments = null;
62
63 9
                if (\is_object($object)) {
64 5
                    if (!$method) {
65 3
                        if (!\is_callable($object)) {
66 1
                            $this->throwAutowireException($target, sprintf('Object `%s` is not callable.', $object::class));
67
                        }
68
69 2
                        if (!($object instanceof \Closure)) {
70 1
                            $method = '__invoke';
71
                        }
72
                    }
73
74 4
                    if (!\is_string($method)) {
75 2
                        $this->throwAutowireException($target, '');
76
                    }
77
78 2
                    $arguments = $this->resolveArguments($object, $method);
79
                }
80
81 6
                if (($object instanceof \Closure) && !$method) {
82
                    return \call_user_func($object, ...$arguments);
83
                }
84
85 6
                if (!$object || !$method) {
86 3
                    $this->throwAutowireException($target, '');
87
                }
88
89 3
                if (\is_string($object)) {
90 1
                    $object = $this->resolveStringAsObject($object);
91
                }
92
93 3
                if (!\is_string($method)) {
94 1
                    $this->throwAutowireException($target, '');
95
                }
96
97 2
                return \call_user_func([$object, $method], ...$arguments); // @phpstan-ignore-line
98 11
            } catch (\InvalidArgumentException $exception) {
99 2
                $this->throwAutowireException($target, '', $exception);
100 9
            } catch (AutowireException $exception) {
101 9
                throw $exception;
102
            }
103 19
        };
104
    }
105
106 1
    protected function resolveStringAsObject(string $target): object
107
    {
108 1
        if (!class_exists($target)) {
109
            $this->throwAutowireException($target, 'The target class does not exist or no callable.');
110
        }
111
112 1
        return new $target(...$this->resolveArguments($target));
113
    }
114
115
    /**
116
     * @throws ContainerExceptionInterface
117
     *
118
     * @phpstan-ignore-next-line
119
     */
120 13
    protected function throwAutowireException(string|array|callable $target, string $message, \Throwable $parent = null): void
121
    {
122 13
        if (\is_array($target)) {
0 ignored issues
show
introduced by
The condition is_array($target) is always true.
Loading history...
123 9
            $target = $target[0] ?? null;
124
        }
125
126 13
        if (\is_callable($target) && !\is_string($target)) {
127 3
            $target = 'Anonymous';
128
        }
129
130 13
        if (\is_object($target)) {
131 2
            $target = $target::class;
132
        }
133
134 13
        throw new AutowireException(sprintf('Can not autowire "%s". %s', $target, $message), 0, $parent);
135
    }
136
137
    /**
138
     * @phpstan-ignore-next-line
139
     */
140 9
    protected function resolveArguments(string|array|object $target, string $method = null): array
141
    {
142 9
        if (\is_callable($target) && !$method && !\is_string($target)) {
143 8
            $ref = new \ReflectionFunction($target); // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
$target of type array is incompatible with the type Closure|string expected by parameter $function of ReflectionFunction::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

143
            $ref = new \ReflectionFunction(/** @scrutinizer ignore-type */ $target); // @phpstan-ignore-line
Loading history...
144
145 8
            return $this->resolveArgumentsFromReflectionParametersObject($ref->getParameters());
146
        }
147
148 4
        $reflectionClass = new \ReflectionClass($target); // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
$target of type array is incompatible with the type object|string expected by parameter $objectOrClass of ReflectionClass::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
        $reflectionClass = new \ReflectionClass(/** @scrutinizer ignore-type */ $target); // @phpstan-ignore-line
Loading history...
149
150 4
        $reflectionClassMethod = null === $method ?
151 2
            $reflectionClass->getConstructor() :
152 2
            $reflectionClass->getMethod($method)
153 4
        ;
154
155 4
        if (null === $reflectionClassMethod) {
156
            return [];
157
        }
158
159 4
        return $this->resolveArgumentsFromReflectionParametersObject(
160 4
            $reflectionClassMethod->getParameters()
161 4
        );
162
    }
163
164
    /**
165
     * @throws \Psr\Container\ContainerExceptionInterface
166
     * @throws NotFoundExceptionInterface
167
     *
168
     * @phpstan-ignore-next-line
169
     */
170 9
    private function resolveArgumentsFromReflectionParametersObject(array $reflectionParameters): array
171
    {
172 9
        $arguments = [];
173
        /** @var \ReflectionParameter $parameter */
174 9
        foreach ($reflectionParameters as $parameter) {
175 8
            $parameterType = $parameter->getType();
176 8
            $allowedNull = $parameter->isOptional();
177 8
            $parameterName = $parameter->getName();
178 8
            if (!$parameterType) {
179 1
                if (!$allowedNull) {
180 1
                    throw new \InvalidArgumentException(sprintf('The untyped argument `%s` cannot be autowired.', $parameterName));
181
                }
182
183
                $arguments[] = null;
184
185
                continue;
186
            }
187
188
            if (
189 7
                !$parameterType instanceof \ReflectionNamedType
190
            ) {
191 1
                throw new \InvalidArgumentException(sprintf('The argument `%s` has invalid type.', $parameterName));
192
            }
193
194 6
            $parameterTypeName = $parameterType->getName();
195
196 6
            $classImplements = class_implements($parameterTypeName);
197 6
            if (false === $classImplements) {
198
                $arguments[] = null;
199
200
                continue;
201
            }
202
203 6
            if (\in_array(ContainerInterface::class, $classImplements)) {
204 6
                $arguments[] = $this->container;
205
206 6
                continue;
207
            }
208
209 6
            $arguments[] = $this->container->get($parameterTypeName);
210
        }
211
212 7
        return $arguments;
213
    }
214
}
215