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

DefinitionStorage::__construct()   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 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 1
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
                /** @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