Passed
Push — master ( ba48b8...05b6a0 )
by Alexander
02:42
created

FactoryInternalContainer::has()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
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
     *
68
     * @return self
69
     */
70 1
    public function withDefinitions(array $definitions): self
71
    {
72 1
        $new = clone $this;
73 1
        $new->definitions = $definitions;
74 1
        $new->definitionInstances = [];
75 1
        $new->creatingIds = [];
76 1
        return $new;
77
    }
78
79
    /**
80
     * @inheritDoc
81
     *
82
     * @param string $id
83
     *
84
     * @return mixed|object
85
     * @psalm-suppress InvalidThrow
86
     */
87 52
    public function get($id)
88
    {
89 52
        if ($this->hasDefinition($id)) {
90 33
            return $this->build($id);
91
        }
92
93 22
        if ($this->container->has($id)) {
94 15
            return $this->container->get($id);
95
        }
96
97 7
        throw new NotInstantiableClassException($id);
98
    }
99
100 2
    public function has($id): bool
101
    {
102 2
        return $this->hasDefinition($id) || $this->container->has($id);
103
    }
104
105
    /**
106
     * @return mixed
107
     */
108 93
    public function create(DefinitionInterface $definition)
109
    {
110 93
        if ($definition instanceof ArrayDefinition) {
111 74
            $this->creatingIds[$definition->getClass()] = 1;
112
        }
113
114
        try {
115
            /** @var mixed $result */
116 93
            $result = $definition->resolve($this);
117 81
            return is_object($result) ? clone $result : $result;
118
        } finally {
119 93
            if ($definition instanceof ArrayDefinition) {
120 93
                unset($this->creatingIds[$definition->getClass()]);
121
            }
122
        }
123
    }
124
125
    /**
126
     * Get definition by identifier provided.
127
     *
128
     * @throws InvalidConfigException
129
     */
130 71
    public function getDefinition(string $id): DefinitionInterface
131
    {
132 71
        if (!isset($this->definitionInstances[$id])) {
133 71
            if (isset($this->definitions[$id])) {
134 70
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
135 13
                    return Normalizer::normalize($this->definitions[$id], $id);
136
                }
137
138
                if (
139 65
                    is_string($this->definitions[$id])
140 65
                    && $this->hasDefinition($this->definitions[$id])
141 65
                    && $this->definitions[$id] !== $this->definitions[$this->definitions[$id]]
142
                ) {
143 1
                    $this->definitionInstances[$id] = $this->getDefinition($this->definitions[$id]);
144
                } else {
145 64
                    $this->definitionInstances[$id] =
146 65
                        (is_string($this->definitions[$id]) && class_exists($this->definitions[$id]))
147 29
                            ? ArrayDefinition::fromPreparedData($this->definitions[$id])
148 65
                            : Normalizer::normalize($this->definitions[$id], $id);
149
                }
150
            } else {
151 1
                throw new LogicException(
152 1
                    sprintf('No definition found for "%s".', $id)
153
                );
154
            }
155
        }
156
157 64
        return $this->definitionInstances[$id];
158
    }
159
160
    /**
161
     * Check if there is a definition with a given identifier.
162
     *
163
     * @param string $id Identifier to look for.
164
     *
165
     * @return bool If there is a definition with a given identifier.
166
     */
167 99
    public function hasDefinition(string $id): bool
168
    {
169 99
        return array_key_exists($id, $this->definitions);
170
    }
171
172
    /**
173
     * @param string $id
174
     *
175
     * @throws CircularReferenceException
176
     * @throws InvalidConfigException
177
     * @throws NotFoundException
178
     * @throws NotInstantiableException
179
     *
180
     * @return mixed|object
181
     */
182 33
    private function build(string $id)
183
    {
184 33
        if (isset($this->creatingIds[$id])) {
185 5
            throw new CircularReferenceException(sprintf(
186 5
                'Circular reference to "%s" detected while creating: %s.',
187
                $id,
188 5
                implode(', ', array_keys($this->creatingIds))
189
            ));
190
        }
191
192 32
        $definition = $this->getDefinition($id);
193
194 32
        $this->creatingIds[$id] = 1;
195
        try {
196 32
            return $definition->resolve($this);
197
        } finally {
198 32
            unset($this->creatingIds[$id]);
199
        }
200
    }
201
}
202