Passed
Pull Request — master (#250)
by Alexander
08:15 queued 05:45
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 2
Bugs 0 Features 0
Metric Value
cc 29
eloc 69
c 2
b 0
f 0
nc 84
nop 2
dl 0
loc 126
ccs 41
cts 66
cp 0.6212
crap 74.7115
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\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