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

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