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

DefinitionStorage::get()   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 0
Metric Value
cc 1
eloc 1
c 0
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
 * @internal
17
 */
18
final class DefinitionStorage
19
{
20
    private array $definitions;
21
    private array $building = [];
22
    /** @psalm-suppress  PropertyNotSetInConstructor */
23
    private ContainerInterface $delegateContainer;
24
25 97
    public function __construct(array $definitions = [])
26
    {
27 97
        $this->definitions = $definitions;
28 97
    }
29
30 90
    public function setDelegateContainer(ContainerInterface $delegateContainer): void
31
    {
32 90
        $this->delegateContainer = $delegateContainer;
33 90
    }
34
35
    /**
36
     * @param string $id class name, interface name or alias name
37
     *
38
     * @throws CircularReferenceException
39
     */
40 91
    public function has(string $id): bool
41
    {
42 91
        if (isset($this->definitions[$id])) {
43 91
            return true;
44
        }
45
46 57
        if (!class_exists($id)) {
47 14
            return false;
48
        }
49
50 52
        if (isset($this->building[$id])) {
51 4
            throw new CircularReferenceException(sprintf(
52 4
                'Circular reference to "%s" detected while building: %s.',
53
                $id,
54 4
                implode(', ', array_keys($this->building))
55
            ));
56
        }
57
58
        try {
59 52
            $reflectionClass = new ReflectionClass($id);
60
        } catch (ReflectionException $e) {
61
            return false;
62
        }
63
64 52
        if (!$reflectionClass->isInstantiable()) {
65
            return false;
66
        }
67
68 52
        $constructor = $reflectionClass->getConstructor();
69
70 52
        if ($constructor === null) {
71 5
            $this->definitions[$id] = $id;
72 5
            return true;
73
        }
74
75 50
        $isResolvable = true;
76 50
        $this->building[$id] = 1;
77
78 50
        foreach ($constructor->getParameters() as $parameter) {
79 50
            $type = $parameter->getType();
80
81 50
            if ($parameter->isVariadic() || $parameter->isOptional()) {
82 44
                break;
83
            }
84
85
            /**
86
             * @var ReflectionNamedType|ReflectionUnionType|null $type
87
             * @psalm-suppress RedundantConditionGivenDocblockType
88
             * @psalm-suppress UndefinedClass
89
             */
90 20
            if ($type === null || !$type instanceof ReflectionUnionType && $type->isBuiltin()) {
91 4
                $isResolvable = false;
92 4
                break;
93
            }
94
95
            // PHP 8 union type is used as type hint
96
            /** @psalm-suppress UndefinedClass, TypeDoesNotContainType */
97 20
            if ($type instanceof ReflectionUnionType) {
98
                $isUnionTypeResolvable = false;
99
                /** @var ReflectionNamedType $unionType */
100
                foreach ($type->getTypes() as $unionType) {
101
                    if (!$unionType->isBuiltin()) {
102
                        $typeName = $unionType->getName();
103
                        if ($typeName === 'self') {
104
                            continue;
105
                        }
106
                        if ($this->has($typeName)) {
107
                            $isUnionTypeResolvable = true;
108
                            break;
109
                        }
110
                    }
111
                }
112
113
                $isResolvable = $isUnionTypeResolvable;
114
                if (!$isResolvable) {
115
                    break;
116
                }
117
                continue;
118
            }
119
120
            /** @var ReflectionNamedType|null $type */
121
            // Our parameter has a class type hint
122 20
            if ($type !== null && !$type->isBuiltin()) {
123 20
                $typeName = $type->getName();
124
125 20
                if ($typeName === 'self') {
126 1
                    throw new CircularReferenceException(sprintf(
127 1
                        'Circular reference to "%s" detected while building: %s.',
128
                        $id,
129 1
                        implode(', ', array_keys($this->building))
130
                    ));
131
                }
132
133
                /** @psalm-suppress RedundantPropertyInitializationCheck */
134 20
                if (!($this->has($typeName) || (isset($this->delegateContainer) ? $this->delegateContainer->has($typeName) : false))) {
135 7
                    $isResolvable = false;
136 7
                    break;
137
                }
138
            }
139
        }
140
141 47
        if ($isResolvable) {
142 44
            $this->definitions[$id] = $id;
143
        }
144
145 47
        unset($this->building[$id]);
146
147 47
        return $isResolvable;
148
    }
149
150
    /**
151
    * @return mixed|object
152
    */
153 91
    public function get(string $id)
154
    {
155 91
        return $this->definitions[$id];
156
    }
157
158
    /**
159
     * @param mixed|object $definition
160
     */
161 97
    public function set(string $id, $definition): void
162
    {
163 97
        $this->definitions[$id] = $definition;
164 97
    }
165
}
166