Passed
Pull Request — master (#248)
by Dmitriy
02:20
created

DefinitionStorage::isResolvable()   D

Complexity

Conditions 29
Paths 84

Size

Total Lines 123
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 69.929

Importance

Changes 0
Metric Value
cc 29
eloc 66
c 0
b 0
f 0
nc 84
nop 2
dl 0
loc 123
ccs 40
cts 63
cp 0.6349
crap 69.929
rs 4.1666

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\Factory\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 $lastBuilding = [];
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->lastBuilding = [];
46 88
        return $this->isResolvable($id, []);
47
    }
48
49 5
    public function getLastBuilding(): array
50
    {
51 5
        return $this->lastBuilding;
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
            return false;
86
        }
87
88 49
        if (isset($building[$id])) {
89 4
            throw new CircularReferenceException(sprintf(
90 4
                'Circular reference to "%s" detected while building: %s.',
91
                $id,
92 4
                implode(', ', array_keys($building))
93
            ));
94
        }
95
96
        try {
97 49
            $reflectionClass = new ReflectionClass($id);
98
        } catch (ReflectionException $e) {
99
            return false;
100
        }
101
102 49
        if (!$reflectionClass->isInstantiable()) {
103
            return false;
104
        }
105
106 49
        $constructor = $reflectionClass->getConstructor();
107
108 49
        if ($constructor === null) {
109 5
            $this->definitions[$id] = $id;
110 5
            return true;
111
        }
112
113 47
        $isResolvable = true;
114 47
        $building[$id] = 1;
115
116
        try {
117 47
            foreach ($constructor->getParameters() as $parameter) {
118 47
                $type = $parameter->getType();
119
120 47
                if ($parameter->isVariadic() || $parameter->isOptional()) {
121 41
                    break;
122
                }
123
124
                /**
125
                 * @var ReflectionNamedType|ReflectionUnionType|null $type
126
                 * @psalm-suppress RedundantConditionGivenDocblockType
127
                 * @psalm-suppress UndefinedClass
128
                 */
129 17
                if ($type === null || !$type instanceof ReflectionUnionType && $type->isBuiltin()) {
130 1
                    $isResolvable = false;
131 1
                    break;
132
                }
133
134
                // PHP 8 union type is used as type hint
135
                /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
136 17
                if ($type instanceof ReflectionUnionType) {
137
                    $isUnionTypeResolvable = false;
138
                    $unionTypes = [];
139
                    /** @var ReflectionNamedType $unionType */
140
                    foreach ($type->getTypes() as $unionType) {
141
                        if (!$unionType->isBuiltin()) {
142
                            $typeName = $unionType->getName();
143
                            if ($typeName === 'self') {
144
                                continue;
145
                            }
146
                            $unionTypes[] = $typeName;
147
                            if ($this->isResolvable($typeName, $building)) {
148
                                $isUnionTypeResolvable = true;
149
                                break;
150
                            }
151
                        }
152
                    }
153
154
155
                    if (!$isUnionTypeResolvable) {
156
                        foreach ($unionTypes as $typeName) {
157
                            if ($this->delegateContainer->has($typeName)) {
158
                                $isUnionTypeResolvable = true;
159
                                break;
160
                            }
161
                        }
162
163
                        $isResolvable = $isUnionTypeResolvable;
164
                        if (!$isResolvable) {
165
                            break;
166
                        }
167
                    }
168
                    continue;
169
                }
170
171
                /** @var ReflectionNamedType|null $type */
172
                // Our parameter has a class type hint
173 17
                if ($type !== null && !$type->isBuiltin()) {
174 17
                    $typeName = $type->getName();
175
176 17
                    if ($typeName === 'self') {
177 1
                        throw new CircularReferenceException(sprintf(
178 1
                            'Circular reference to "%s" detected while building: %s.',
179
                            $id,
180 1
                            implode(', ', array_keys($building))
181
                        ));
182
                    }
183
184
                    /** @psalm-suppress RedundantPropertyInitializationCheck */
185 17
                    if (!($this->isResolvable($typeName, $building) || (isset($this->delegateContainer) ? $this->delegateContainer->has($typeName) : false))) {
186 7
                        $isResolvable = false;
187 7
                        break;
188
                    }
189
                }
190
            }
191 44
        } finally {
192 47
            $this->lastBuilding += $building;
193 47
            unset($building[$id]);
194
        }
195
196 44
        if ($isResolvable) {
197 41
            $this->definitions[$id] = $id;
198
        }
199
200 44
        return $isResolvable;
201
    }
202
}
203