Passed
Pull Request — master (#138)
by Sergei
10:54 queued 03:07
created

DependencyResolver::createDefinition()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

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