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