Passed
Push — master ( 953b3a...731eeb )
by Alexander
02:48
created

DefinitionStorage::isResolvable()   D

Complexity

Conditions 29
Paths 79

Size

Total Lines 124
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 55.917

Importance

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