Passed
Push — master ( bc8062...4261d5 )
by Alexander
02:10
created

DependencyResolver::create()   A

Complexity

Conditions 3
Paths 8

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 8
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use Psr\Container\NotFoundExceptionInterface;
10
use Yiisoft\Factory\Definition\ArrayDefinition;
11
use Yiisoft\Factory\Definition\DefinitionInterface;
12
use Yiisoft\Factory\Definition\Normalizer;
13
use Yiisoft\Factory\Exception\CircularReferenceException;
14
use Yiisoft\Factory\Exception\InvalidConfigException;
15
use Yiisoft\Factory\Exception\NotFoundException;
16
use Yiisoft\Factory\Exception\NotInstantiableException;
17
use Yiisoft\Injector\Injector;
18
19
use function is_object;
20
use function is_string;
21
22
/**
23
 * @internal
24
 */
25
final class DependencyResolver implements DependencyResolverInterface
26
{
27
    private ?ContainerInterface $container;
28
    private ?Injector $injector = null;
29
30
    /**
31
     * @var mixed[] Definitions
32
     * @psalm-var array<string, mixed>
33
     */
34
    private array $definitions = [];
35
36
    /**
37
     * @var DefinitionInterface[] object definitions indexed by their types
38
     * @psalm-var array<string, DefinitionInterface>
39
     */
40
    private array $definitionInstances = [];
41
42
    /**
43
     * @var array used to collect IDs instantiated during build to detect circular references
44
     *
45
     * @psalm-var array<string,1>
46
     */
47
    private array $creatingIds = [];
48
49 112
    public function __construct(?ContainerInterface $container)
50
    {
51 112
        $this->container = $container;
52 112
    }
53
54
    /**
55
     * @param string $id
56
     *
57
     * @throws NotFoundExceptionInterface
58
     * @throws ContainerExceptionInterface
59
     *
60
     * @return mixed|object
61
     *
62
     * @psalm-suppress InvalidThrow
63
     */
64 28
    public function get($id)
65
    {
66 28
        if ($this->container !== null) {
67 8
            return $this->container->get($id);
68
        }
69 20
        return $this->getFromFactory($id);
70
    }
71
72
    /**
73
     * @param string $id
74
     */
75 2
    public function has($id): bool
76
    {
77 2
        if ($this->container !== null) {
78 1
            return $this->container->has($id);
79
        }
80 1
        return $this->canBeCreatedByFactory($id);
81
    }
82
83 27
    public function resolveReference(string $id)
84
    {
85 27
        return $this->getFromFactory($id);
86
    }
87
88 14
    public function invoke(callable $callable)
89
    {
90 14
        return $this->getInjector()->invoke($callable);
91
    }
92
93
    /**
94
     * @param mixed $definition
95
     */
96 85
    public function setFactoryDefinition(string $id, $definition): void
97
    {
98 85
        $this->definitions[$id] = $definition;
99 85
    }
100
101
    /**
102
     * @param mixed $config
103
     *
104
     * @throws CircularReferenceException
105
     * @throws NotFoundException
106
     * @throws NotInstantiableException
107
     * @throws InvalidConfigException
108
     *
109
     * @return mixed
110
     */
111 73
    public function create($config)
112
    {
113 73
        $definition = $this->createDefinition($config);
114
115 70
        if ($definition instanceof ArrayDefinition) {
116 48
            $this->creatingIds[$definition->getClass()] = 1;
117
        }
118
        try {
119 70
            return $definition->resolve($this);
120
        } finally {
121 70
            if ($definition instanceof ArrayDefinition) {
122 70
                unset($this->creatingIds[$definition->getClass()]);
123
            }
124
        }
125
    }
126
127
    /**
128
     * @param mixed $config
129
     *
130
     * @throws InvalidConfigException
131
     */
132 73
    private function createDefinition($config): DefinitionInterface
133
    {
134 73
        if (is_string($config) && isset($this->definitions[$config])) {
135 43
            return Normalizer::normalize(
136 43
                is_object($this->definitions[$config])
137 11
                    ? clone $this->definitions[$config]
138 43
                    : $this->definitions[$config],
139
                $config
140
            );
141
        }
142
143 30
        $definition = Normalizer::normalize($config);
144
145
        if (
146 29
            ($definition instanceof ArrayDefinition) &&
147 29
            isset($this->definitions[$definition->getClass()])
148
        ) {
149 4
            $definition = $this->mergeDefinitions(
150 4
                $this->getDefinition($definition->getClass()),
151
                $definition
152
            );
153
        }
154
155 29
        return $definition;
156
    }
157
158
    /**
159
     * @param string $id
160
     *
161
     * @throws CircularReferenceException
162
     * @throws InvalidConfigException
163
     * @throws NotFoundException
164
     * @throws NotInstantiableException
165
     *
166
     * @return mixed|object
167
     */
168 37
    private function getFromFactory(string $id)
169
    {
170 37
        if (isset($this->creatingIds[$id])) {
171 5
            if ($id === ContainerInterface::class) {
172
                return $this;
173
            }
174 5
            throw new CircularReferenceException(sprintf(
175 5
                'Circular reference to "%s" detected while creating: %s.',
176
                $id,
177 5
                implode(',', array_keys($this->creatingIds))
178
            ));
179
        }
180
181 36
        $this->creatingIds[$id] = 1;
182
        try {
183 36
            return $this->getDefinition($id)->resolve($this);
184
        } finally {
185 36
            unset($this->creatingIds[$id]);
186
        }
187
    }
188
189
    /**
190
     * @throws InvalidConfigException
191
     */
192 40
    private function getDefinition(string $id): DefinitionInterface
193
    {
194 40
        if (!isset($this->definitionInstances[$id])) {
195 40
            if (isset($this->definitions[$id])) {
196 26
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
197
            } else {
198
                /** @psalm-var class-string $id */
199 31
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
200
            }
201
        }
202
203 40
        return $this->definitionInstances[$id];
204
    }
205
206 14
    private function getInjector(): Injector
207
    {
208 14
        return $this->injector ??= new Injector($this);
209
    }
210
211 1
    private function canBeCreatedByFactory(string $id): bool
212
    {
213 1
        return isset($this->definitions[$id]) || class_exists($id);
214
    }
215
216 4
    private function mergeDefinitions(DefinitionInterface $one, ArrayDefinition $two): DefinitionInterface
217
    {
218 4
        return $one instanceof ArrayDefinition ? $one->merge($two) : $two;
219
    }
220
}
221