Passed
Push — master ( 6a2fee...c38029 )
by Alexander
05:10 queued 03:00
created

DefinitionStorage::isResolvable()   D

Complexity

Conditions 29
Paths 79

Size

Total Lines 124
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 119.8163

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 29
eloc 66
c 1
b 0
f 0
nc 79
nop 2
dl 0
loc 124
ccs 33
cts 63
cp 0.5238
crap 119.8163
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\Definitions\Infrastructure;
6
7
use Psr\Container\ContainerInterface;
8
use ReflectionNamedType;
9
use ReflectionUnionType;
10
use RuntimeException;
11
use Yiisoft\Definitions\Exception\CircularReferenceException;
12
use Yiisoft\Definitions\Infrastructure\DefinitionExtractor;
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 = null;
25
26 8
    public function __construct(array $definitions = [])
27
    {
28 8
        $this->definitions = $definitions;
29 8
    }
30
31 1
    public function setDelegateContainer(ContainerInterface $delegateContainer): void
32
    {
33 1
        $this->delegateContainer = $delegateContainer;
34 1
    }
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 8
    public function has(string $id): bool
44
    {
45 8
        $this->buildStack = [];
46 8
        return $this->isResolvable($id, []);
47
    }
48
49 8
    public function getBuildStack(): array
50
    {
51 8
        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
    public function get(string $id)
60
    {
61
        if (!isset($this->definitions[$id])) {
62
            throw new RuntimeException("Service $id doesn't exist in DefinitionStorage.");
63
        }
64
        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
    public function set(string $id, $definition): void
74
    {
75
        $this->definitions[$id] = $definition;
76
    }
77
78 8
    private function isResolvable(string $id, array $building): bool
79
    {
80 8
        if (isset($this->definitions[$id])) {
81 1
            return true;
82
        }
83
84 7
        if (!class_exists($id)) {
85 4
            $this->buildStack += $building + [$id => 1];
86 4
            return false;
87
        }
88
89 5
        if (isset($building[$id])) {
90
            throw new CircularReferenceException(sprintf(
91
                'Circular reference to "%s" detected while building: %s.',
92
                $id,
93
                implode(', ', array_keys($building))
94
            ));
95
        }
96
97
        try {
98 5
            $dependencies = DefinitionExtractor::getInstance()->fromClassName($id);
99 2
        } catch (\Throwable $e) {
100 2
            $this->buildStack += $building + [$id => 1];
101 2
            return false;
102
        }
103
104 4
        if ($dependencies === []) {
105
            $this->definitions[$id] = $id;
106
            return true;
107
        }
108
109 4
        $isResolvable = true;
110 4
        $building[$id] = 1;
111
112
        try {
113 4
            foreach ($dependencies as $dependency) {
114 4
                $parameter = $dependency->getReflection();
115 4
                $type = $parameter->getType();
116
117 4
                if ($parameter->isVariadic() || $parameter->isOptional()) {
118
                    break;
119
                }
120
121
                /**
122
                 * @var ReflectionNamedType|ReflectionUnionType|null $type
123
                 * @psalm-suppress RedundantConditionGivenDocblockType
124
                 * @psalm-suppress UndefinedClass
125
                 */
126 4
                if ($type === null || !$type instanceof ReflectionUnionType && $type->isBuiltin()) {
127 1
                    $isResolvable = false;
128 1
                    break;
129
                }
130
131
                // PHP 8 union type is used as type hint
132
                /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
133 3
                if ($type instanceof ReflectionUnionType) {
134
                    $isUnionTypeResolvable = false;
135
                    $unionTypes = [];
136
                    /** @var ReflectionNamedType $unionType */
137
                    foreach ($type->getTypes() as $unionType) {
138
                        if (!$unionType->isBuiltin()) {
139
                            $typeName = $unionType->getName();
140
                            if ($typeName === 'self') {
141
                                continue;
142
                            }
143
                            $unionTypes[] = $typeName;
144
                            if ($this->isResolvable($typeName, $building)) {
145
                                $isUnionTypeResolvable = true;
146
                                break;
147
                            }
148
                        }
149
                    }
150
151
152
                    if (!$isUnionTypeResolvable) {
153
                        foreach ($unionTypes as $typeName) {
154
                            if ($this->delegateContainer !== null && $this->delegateContainer->has($typeName)) {
155
                                $isUnionTypeResolvable = true;
156
                                break;
157
                            }
158
                        }
159
160
                        $isResolvable = $isUnionTypeResolvable;
161
                        if (!$isResolvable) {
162
                            break;
163
                        }
164
                    }
165
                    continue;
166
                }
167
168
                /** @var ReflectionNamedType|null $type */
169
                // Our parameter has a class type hint
170 3
                if ($type !== null && !$type->isBuiltin()) {
171 3
                    $typeName = $type->getName();
172
                    /**
173
                     * @psalm-suppress TypeDoesNotContainType
174
                     *
175
                     * @link https://github.com/vimeo/psalm/issues/6756
176
                     */
177 3
                    if ($typeName === 'self') {
178
                        throw new CircularReferenceException(sprintf(
179
                            'Circular reference to "%s" detected while building: %s.',
180
                            $id,
181
                            implode(', ', array_keys($building))
182
                        ));
183
                    }
184
185
                    /** @psalm-suppress RedundantPropertyInitializationCheck */
186 3
                    if (!$this->isResolvable($typeName, $building) && ($this->delegateContainer === null || !$this->delegateContainer->has($typeName))) {
187 3
                        $isResolvable = false;
188 3
                        break;
189
                    }
190
                }
191
            }
192 4
        } finally {
193 4
            $this->buildStack += $building;
194 4
            unset($building[$id]);
195
        }
196
197 4
        if ($isResolvable) {
198
            $this->definitions[$id] = $id;
199
        }
200
201 4
        return $isResolvable;
202
    }
203
}
204