Passed
Push — master ( ff7626...33d9d1 )
by Alexander
03:05 queued 37s
created

DependencyResolver::create()   B

Complexity

Conditions 7
Paths 34

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 15
c 0
b 0
f 0
nc 34
nop 1
dl 0
loc 22
ccs 14
cts 14
cp 1
crap 7
rs 8.8333
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;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Definitions\Cont...ndencyResolverInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 ContainerInterface
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 86
    public function __construct(?ContainerInterface $container)
51
    {
52 86
        $this->container = $container;
53 86
    }
54
55
    /**
56
     * @throws NotFoundExceptionInterface
57
     * @throws ContainerExceptionInterface
58
     *
59
     * @return mixed|object
60
     *
61
     * @psalm-suppress InvalidThrow
62
     */
63 32
    public function get($id)
64
    {
65 32
        if (isset($this->definitions[$id])) {
66 22
            return $this->getFromFactory($id);
67
        }
68
69 25
        if (class_exists($id)) {
70 23
            return $this->getFromFactory($id);
71
        }
72
73 3
        throw new NotInstantiableClassException($id);
74
    }
75
76
    public function has($id): bool
77
    {
78
        return isset($this->definitions[$id]) || ($this->container !== null && $this->container->has($id)) || class_exists($id);
79
    }
80
81
    /**
82
     * @param mixed $definition
83
     */
84 51
    public function setFactoryDefinition(string $id, $definition): void
85
    {
86 51
        $this->definitions[$id] = $definition;
87 51
    }
88
89
    /**
90
     * @param mixed $config
91
     *
92
     * @throws CircularReferenceException
93
     * @throws NotFoundException
94
     * @throws NotInstantiableException
95
     * @throws InvalidConfigException
96
     *
97
     * @return mixed
98
     */
99 79
    public function create($config)
100
    {
101 79
        if (is_string($config)) {
102 62
            if ($this->canBeCreatedByFactory($config)) {
103 59
                return $this->getFromFactory($config);
104
            }
105 3
            throw new NotFoundException($config);
106
        }
107
108 19
        $definition = $this->createDefinition($config);
109
110 18
        if ($definition instanceof ArrayDefinition) {
111 16
            $definition->setReferenceContainer($this);
112 16
            $this->creatingIds[$definition->getClass()] = 1;
113
        }
114
        try {
115 18
            $container = ($this->container === null || $definition instanceof ReferenceInterface) ? $this : $this->container;
116 18
            return $definition->resolve($container);
117
        } finally {
118 18
            if ($definition instanceof ArrayDefinition) {
119 16
                unset($this->creatingIds[$definition->getClass()]);
120 18
                $definition->setReferenceContainer(null);
121
            }
122
        }
123
    }
124
125
    /**
126
     * @param mixed $config
127
     *
128
     * @throws InvalidConfigException
129
     */
130 19
    private function createDefinition($config): DefinitionInterface
131
    {
132 19
        $definition = Normalizer::normalize($config);
133
134
        if (
135 18
            ($definition instanceof ArrayDefinition) &&
136 18
            isset($this->definitions[$definition->getClass()])
137
        ) {
138 4
            $definition = $this->mergeDefinitions(
139 4
                $this->getDefinition($definition->getClass()),
140
                $definition
141
            );
142
        }
143
144 18
        return $definition;
145
    }
146
147
    /**
148
     * @param string $id
149
     *
150
     * @throws CircularReferenceException
151
     * @throws InvalidConfigException
152
     * @throws NotFoundException
153
     * @throws NotInstantiableException
154
     *
155
     * @return mixed|object
156
     */
157 60
    private function getFromFactory(string $id)
158
    {
159 60
        if (isset($this->creatingIds[$id])) {
160 5
            throw new CircularReferenceException(sprintf(
161 5
                'Circular reference to "%s" detected while creating: %s.',
162
                $id,
163 5
                implode(',', array_keys($this->creatingIds))
164
            ));
165
        }
166
167 60
        $definition = $this->getDefinition($id);
168 58
        if ($definition instanceof ArrayDefinition) {
169 52
            $definition->setReferenceContainer($this);
170
        }
171 58
        $this->creatingIds[$id] = 1;
172
        try {
173 58
            $container = ($this->container === null || $definition instanceof ReferenceInterface) ? $this : $this->container;
174
            try {
175 58
                return $definition->resolve($container);
176 9
            } catch (NotFoundExceptionInterface $e) {
177 1
                if ($container === $this) {
178
                    throw $e;
179
                }
180
181 1
                return $definition->resolve($this);
182
            }
183
        } finally {
184 58
            unset($this->creatingIds[$id]);
185 58
            if ($definition instanceof ArrayDefinition) {
186 58
                $definition->setReferenceContainer(null);
187
            }
188
        }
189
    }
190
191
    /**
192
     * @throws InvalidConfigException
193
     */
194 64
    private function getDefinition(string $id): DefinitionInterface
195
    {
196 64
        if (!isset($this->definitionInstances[$id])) {
197 64
            if (isset($this->definitions[$id])) {
198 51
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
199 9
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
200
                }
201 47
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
202
            } else {
203
                /** @psalm-var class-string $id */
204 33
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
205
            }
206
        }
207
208 59
        return $this->definitionInstances[$id];
209
    }
210
211 62
    private function canBeCreatedByFactory(string $id): bool
212
    {
213 62
        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