Passed
Push — master ( db5893...1bb971 )
by Alexander
07:58 queued 05:11
created

DefinitionStorage::isResolvable()   D

Complexity

Conditions 29
Paths 84

Size

Total Lines 126
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 74.7115

Importance

Changes 0
Metric Value
cc 29
eloc 69
nc 84
nop 2
dl 0
loc 126
ccs 41
cts 66
cp 0.6212
crap 74.7115
rs 4.1666
c 0
b 0
f 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\Di;
6
7
use Psr\Container\ContainerInterface;
8
use ReflectionClass;
9
use ReflectionException;
10
use ReflectionNamedType;
11
use ReflectionUnionType;
12
use Yiisoft\Definitions\Exception\CircularReferenceException;
13
14
/**
15
 * Stores service definitions and checks if a definition could be instantiated.
16
 *
17
 * @internal
18
 */
19
final class DefinitionStorage
20
{
21
    private array $definitions;
22
    private array $buildStack = [];
23
    /** @psalm-suppress  PropertyNotSetInConstructor */
24
    private ContainerInterface $delegateContainer;
25
26 94
    public function __construct(array $definitions = [])
27
    {
28 94
        $this->definitions = $definitions;
29 94
    }
30
31 87
    public function setDelegateContainer(ContainerInterface $delegateContainer): void
32
    {
33 87
        $this->delegateContainer = $delegateContainer;
34 87
    }
35
36
    /**
37
     * Checks if there is a definition with ID specified and that it can be created.
38
     *
39
     * @param string $id class name, interface name or alias name
40
     *
41
     * @throws CircularReferenceException
42
     */
43 88
    public function has(string $id): bool
44
    {
45 88
        $this->buildStack = [];
46 88
        return $this->isResolvable($id, []);
47
    }
48
49 5
    public function getBuildStack(): array
50
    {
51 5
        return $this->buildStack;
52
    }
53
54
    /**
55
     * Get a definition with a given ID.
56
     *
57
     * @return mixed|object Definition with a given ID.
58
     */
59 88
    public function get(string $id)
60
    {
61 88
        if (!isset($this->definitions[$id])) {
62
            throw new \RuntimeException("Service $id doesn't exist in DefinitionStorage.");
63
        }
64 88
        return $this->definitions[$id];
65
    }
66
67
    /**
68
     * Set a definition.
69
     *
70
     * @param string $id ID to set definition for.
71
     * @param mixed|object $definition Definition to set.
72
     */
73 94
    public function set(string $id, $definition): void
74
    {
75 94
        $this->definitions[$id] = $definition;
76 94
    }
77
78 88
    private function isResolvable(string $id, array $building): bool
79
    {
80 88
        if (isset($this->definitions[$id])) {
81 88
            return true;
82
        }
83
84 55
        if (!class_exists($id)) {
85 15
            $this->buildStack += array_merge($building, [$id => 1]);
86 15
            return false;
87
        }
88
89 49
        if (isset($building[$id])) {
90 4
            throw new CircularReferenceException(sprintf(
91 4
                'Circular reference to "%s" detected while building: %s.',
92
                $id,
93 4
                implode(', ', array_keys($building))
94
            ));
95
        }
96
97
        try {
98 49
            $reflectionClass = new ReflectionClass($id);
99
        } catch (ReflectionException $e) {
100
            $this->buildStack += array_merge($building, [$id => 1]);
101
            return false;
102
        }
103
104 49
        if (!$reflectionClass->isInstantiable()) {
105
            $this->buildStack = array_merge($this->buildStack, [$id => 1]);
106
            return false;
107
        }
108
109 49
        $constructor = $reflectionClass->getConstructor();
110
111 49
        if ($constructor === null) {
112 5
            $this->definitions[$id] = $id;
113 5
            return true;
114
        }
115
116 47
        $isResolvable = true;
117 47
        $building[$id] = 1;
118
119
        try {
120 47
            foreach ($constructor->getParameters() as $parameter) {
121 47
                $type = $parameter->getType();
122
123 47
                if ($parameter->isVariadic() || $parameter->isOptional()) {
124 41
                    break;
125
                }
126
127
                /**
128
                 * @var ReflectionNamedType|ReflectionUnionType|null $type
129
                 * @psalm-suppress RedundantConditionGivenDocblockType
130
                 * @psalm-suppress UndefinedClass
131
                 */
132 17
                if ($type === null || !$type instanceof ReflectionUnionType && $type->isBuiltin()) {
133 1
                    $isResolvable = false;
134 1
                    break;
135
                }
136
137
                // PHP 8 union type is used as type hint
138
                /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
139 17
                if ($type instanceof ReflectionUnionType) {
140
                    $isUnionTypeResolvable = false;
141
                    $unionTypes = [];
142
                    /** @var ReflectionNamedType $unionType */
143
                    foreach ($type->getTypes() as $unionType) {
144
                        if (!$unionType->isBuiltin()) {
145
                            $typeName = $unionType->getName();
146
                            if ($typeName === 'self') {
147
                                continue;
148
                            }
149
                            $unionTypes[] = $typeName;
150
                            if ($this->isResolvable($typeName, $building)) {
151
                                $isUnionTypeResolvable = true;
152
                                break;
153
                            }
154
                        }
155
                    }
156
157
158
                    if (!$isUnionTypeResolvable) {
159
                        foreach ($unionTypes as $typeName) {
160
                            if ($this->delegateContainer->has($typeName)) {
161
                                $isUnionTypeResolvable = true;
162
                                break;
163
                            }
164
                        }
165
166
                        $isResolvable = $isUnionTypeResolvable;
167
                        if (!$isResolvable) {
168
                            break;
169
                        }
170
                    }
171
                    continue;
172
                }
173
174
                /** @var ReflectionNamedType|null $type */
175
                // Our parameter has a class type hint
176 17
                if ($type !== null && !$type->isBuiltin()) {
177 17
                    $typeName = $type->getName();
178
179 17
                    if ($typeName === 'self') {
180 1
                        throw new CircularReferenceException(sprintf(
181 1
                            'Circular reference to "%s" detected while building: %s.',
182
                            $id,
183 1
                            implode(', ', array_keys($building))
184
                        ));
185
                    }
186
187
                    /** @psalm-suppress RedundantPropertyInitializationCheck */
188 17
                    if (!($this->isResolvable($typeName, $building) || (isset($this->delegateContainer) ? $this->delegateContainer->has($typeName) : false))) {
189 7
                        $isResolvable = false;
190 7
                        break;
191
                    }
192
                }
193
            }
194 44
        } finally {
195 47
            $this->buildStack += $building;
196 47
            unset($building[$id]);
197
        }
198
199 44
        if ($isResolvable) {
200 41
            $this->definitions[$id] = $id;
201
        }
202
203 44
        return $isResolvable;
204
    }
205
}
206