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

DefinitionStorage::set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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