Passed
Pull Request — master (#240)
by Dmitriy
02:33
created

DefinitionStorage::has()   D

Complexity

Conditions 29
Paths 43

Size

Total Lines 120
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 74.0629

Importance

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