Passed
Pull Request — master (#156)
by Alexander
06:28
created

FactoryInternalContainer   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 54
dl 0
loc 169
ccs 51
cts 51
cp 1
rs 10
c 1
b 1
f 0
wmc 23

8 Methods

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