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

DefinitionStorage::has()   D

Complexity

Conditions 26
Paths 27

Size

Total Lines 108
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 43.5849

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 26
eloc 56
c 1
b 0
f 0
nc 27
nop 1
dl 0
loc 108
ccs 38
cts 54
cp 0.7037
crap 43.5849
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
                /** @var ReflectionNamedType $unionType */
101
                foreach ($type->getTypes() as $unionType) {
102
                    if (!$unionType->isBuiltin()) {
103
                        $typeName = $unionType->getName();
104
                        if ($typeName === 'self') {
105
                            continue;
106
                        }
107
                        if ($this->has($typeName)) {
108
                            $isUnionTypeResolvable = true;
109
                            break;
110
                        }
111
                    }
112
                }
113
114
                $isResolvable = $isUnionTypeResolvable;
115
                if (!$isResolvable) {
116
                    break;
117
                }
118
                continue;
119
            }
120
121
            /** @var ReflectionNamedType|null $type */
122
            // Our parameter has a class type hint
123 20
            if ($type !== null && !$type->isBuiltin()) {
124 20
                $typeName = $type->getName();
125
126 20
                if ($typeName === 'self') {
127 1
                    throw new CircularReferenceException(sprintf(
128 1
                        'Circular reference to "%s" detected while building: %s.',
129
                        $id,
130 1
                        implode(', ', array_keys($this->building))
131
                    ));
132
                }
133
134
                /** @psalm-suppress RedundantPropertyInitializationCheck */
135 20
                if (!($this->has($typeName) || (isset($this->delegateContainer) ? $this->delegateContainer->has($typeName) : false))) {
136 7
                    $isResolvable = false;
137 7
                    break;
138
                }
139
            }
140
        }
141
142 47
        if ($isResolvable) {
143 44
            $this->definitions[$id] = $id;
144
        }
145
146 47
        unset($this->building[$id]);
147
148 47
        return $isResolvable;
149
    }
150
151
    /**
152
    * @return mixed|object
153
    */
154 91
    public function get(string $id)
155
    {
156 91
        if (!isset($this->definitions[$id])) {
157
            throw new \RuntimeException("Service $id doesn't exist in DefinitionStorage.");
158
        }
159 91
        return $this->definitions[$id];
160
    }
161
162
    /**
163
     * @param mixed|object $definition
164
     */
165 97
    public function set(string $id, $definition): void
166
    {
167 97
        $this->definitions[$id] = $definition;
168 97
    }
169
}
170