Passed
Push — master ( ee2cd0...5882c0 )
by Sergei
28:31 queued 26:09
created

DependencyResolver::resolve()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

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