Passed
Push — master ( e0690f...bab3ce )
by Alexander
02:28
created

DefinitionStorage::has()   D

Complexity

Conditions 29
Paths 84

Size

Total Lines 122
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 71.9455

Importance

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