Passed
Pull Request — master (#151)
by Sergei
02:03
created

FactoryContainer   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Test Coverage

Coverage 93.62%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 47
dl 0
loc 156
ccs 44
cts 47
cp 0.9362
rs 10
c 1
b 1
f 0
wmc 27

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A hasDefinition() 0 12 3
B build() 0 30 8
A getDefinition() 0 15 5
A setDefinition() 0 3 1
A get() 0 11 5
A has() 0 3 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\Container\NotFoundExceptionInterface;
9
use Yiisoft\Definitions\ArrayDefinition;
10
use Yiisoft\Definitions\Contract\DefinitionInterface;
11
use Yiisoft\Definitions\Contract\ReferenceInterface;
12
use Yiisoft\Definitions\Exception\CircularReferenceException;
13
use Yiisoft\Definitions\Exception\InvalidConfigException;
14
use Yiisoft\Definitions\Exception\NotInstantiableClassException;
15
use Yiisoft\Definitions\Exception\NotInstantiableException;
16
use Yiisoft\Definitions\Helpers\Normalizer;
17
18
use function is_object;
19
20
/**
21
 * Factory's primary container.
22
 *
23
 * @internal
24
 */
25
final class FactoryContainer implements ContainerInterface
26
{
27
    /**
28
     * @var ContainerInterface|null Container to use for resolving dependencies. When null, only definitions
29
     * are used.
30
     */
31
    private ?ContainerInterface $container;
32
33
    /**
34
     * @var array Definitions to create objects with.
35
     * @psalm-var array<string, mixed>
36
     */
37
    private array $definitions = [];
38
39
    /**
40
     * @var DefinitionInterface[] Object created from definitions indexed by their types.
41
     * @psalm-var array<string, DefinitionInterface>
42
     */
43
    private array $definitionInstances = [];
44
45
    /**
46
     * @var array Used to collect IDs instantiated during build to detect circular references.
47
     *
48
     * @psalm-var array<string,1>
49
     */
50
    private array $creatingIds = [];
51
52
    /**
53
     * @param ContainerInterface|null $container Container to use for resolving dependencies. When null, only definitions
54
     * are used.
55
     */
56 94
    public function __construct(?ContainerInterface $container)
57
    {
58 94
        $this->container = $container;
59 94
    }
60
61
    /**
62
     * @inheritDoc
63
     *
64
     * @param string $id
65
     *
66
     * @return mixed|object
67
     * @psalm-suppress InvalidThrow
68
     */
69 68
    public function get($id)
70
    {
71 68
        if (isset($this->definitions[$id]) || class_exists($id)) {
72 68
            return $this->build($id);
73
        }
74
75 8
        if ($this->container !== null && $this->container->has($id)) {
76 1
            return $this->container->get($id);
77
        }
78
79 7
        throw new NotInstantiableClassException($id);
80
    }
81
82
    public function has($id): bool
83
    {
84
        return isset($this->definitions[$id]) || ($this->container !== null && $this->container->has($id)) || class_exists($id);
85
    }
86
87
    /**
88
     * Get definition by identifier provided.
89
     *
90
     * @throws InvalidConfigException
91
     */
92 82
    public function getDefinition(string $id): DefinitionInterface
93
    {
94 82
        if (!isset($this->definitionInstances[$id])) {
95 82
            if (isset($this->definitions[$id])) {
96 82
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
97 11
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
98
                }
99 78
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
100
            } else {
101
                /** @psalm-var class-string $id */
102 26
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
103
            }
104
        }
105
106 77
        return $this->definitionInstances[$id];
107
    }
108
109
    /**
110
     * Check if there is a definition with a given identifier.
111
     *
112
     * @param string $id Identifier to look for.
113
     *
114
     * @return bool If there is a definition with a given identifier.
115
     */
116 84
    public function hasDefinition(string $id): bool
117
    {
118 84
        if (isset($this->definitions[$id])) {
119 52
            return true;
120
        }
121
122 36
        if (class_exists($id)) {
123 33
            $this->definitions[$id] = $id;
124 33
            return true;
125
        }
126
127 3
        return false;
128
    }
129
130
    /**
131
     * Set definition for a given identifier.
132
     *
133
     * @param string $id Identifier to set definition for.
134
     * @param mixed $definition Definition to set.
135
     */
136 54
    public function setDefinition(string $id, $definition): void
137
    {
138 54
        $this->definitions[$id] = $definition;
139 54
    }
140
141
    /**
142
     * @param string $id
143
     *
144
     * @throws CircularReferenceException
145
     * @throws InvalidConfigException
146
     * @throws NotFoundException
147
     * @throws NotInstantiableException
148
     *
149
     * @return mixed|object
150
     */
151 68
    private function build(string $id)
152
    {
153 68
        if (isset($this->creatingIds[$id])) {
154 5
            throw new CircularReferenceException(sprintf(
155 5
                'Circular reference to "%s" detected while creating: %s.',
156
                $id,
157 5
                implode(',', array_keys($this->creatingIds))
158
            ));
159
        }
160
161 68
        $definition = $this->getDefinition($id);
162 66
        if ($definition instanceof ArrayDefinition) {
163 58
            $definition->setReferenceContainer($this);
164
        }
165 66
        $this->creatingIds[$id] = 1;
166
        try {
167 66
            $container = ($this->container === null || $definition instanceof ReferenceInterface) ? $this : $this->container;
168
            try {
169 66
                return $definition->resolve($container);
170 14
            } catch (NotFoundExceptionInterface $e) {
171 1
                if ($container === $this) {
172
                    throw $e;
173
                }
174
175 1
                return $definition->resolve($this);
176
            }
177
        } finally {
178 66
            unset($this->creatingIds[$id]);
179 66
            if ($definition instanceof ArrayDefinition) {
180 66
                $definition->setReferenceContainer(null);
181
            }
182
        }
183
    }
184
}
185