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

DefinitionStorage::has()   D

Complexity

Conditions 29
Paths 43

Size

Total Lines 121
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 74.0629

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 29
eloc 63
c 2
b 0
f 0
nc 43
nop 1
dl 0
loc 121
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
                    {
120
                        if ($this->delegateContainer->has($typeName)) {
121
                            $isUnionTypeResolvable = true;
122
                            break;
123
                        }
124
                    }
125
126
                    $isResolvable = $isUnionTypeResolvable;
127
                    if (!$isResolvable) {
128
                        break;
129
                    }
130
                }
131
                continue;
132
            }
133
134
            /** @var ReflectionNamedType|null $type */
135
            // Our parameter has a class type hint
136 20
            if ($type !== null && !$type->isBuiltin()) {
137 20
                $typeName = $type->getName();
138
139 20
                if ($typeName === 'self') {
140 1
                    throw new CircularReferenceException(sprintf(
141 1
                        'Circular reference to "%s" detected while building: %s.',
142
                        $id,
143 1
                        implode(', ', array_keys($this->building))
144
                    ));
145
                }
146
147
                /** @psalm-suppress RedundantPropertyInitializationCheck */
148 20
                if (!($this->has($typeName) || (isset($this->delegateContainer) ? $this->delegateContainer->has($typeName) : false))) {
149 7
                    $isResolvable = false;
150 7
                    break;
151
                }
152
            }
153
        }
154
155 47
        if ($isResolvable) {
156 44
            $this->definitions[$id] = $id;
157
        }
158
159 47
        unset($this->building[$id]);
160
161 47
        return $isResolvable;
162
    }
163
164
    /**
165
    * @return mixed|object
166
    */
167 91
    public function get(string $id)
168
    {
169 91
        if (!isset($this->definitions[$id])) {
170
            throw new \RuntimeException("Service $id doesn't exist in DefinitionStorage.");
171
        }
172 91
        return $this->definitions[$id];
173
    }
174
175
    /**
176
     * @param mixed|object $definition
177
     */
178 97
    public function set(string $id, $definition): void
179
    {
180 97
        $this->definitions[$id] = $definition;
181 97
    }
182
}
183