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

DefinitionStorage::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
ccs 3
cts 4
cp 0.75
crap 2.0625
rs 10
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