Passed
Pull Request — master (#151)
by Alexander
02:14
created

FactoryContainer::hasInContainer()   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
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 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
21
/**
22
 * Factory's primary container.
23
 *
24
 * @internal
25
 */
26
final class FactoryContainer implements ContainerInterface
27
{
28
    /**
29
     * @var ContainerInterface|null Container to use for resolving dependencies. When null, only definitions
30
     * are used.
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|null $container Container to use for resolving dependencies. When null, only definitions
55
     * are used.
56
     */
57 111
    public function __construct(?ContainerInterface $container)
58
    {
59 111
        $this->container = $container;
60 111
    }
61
62
    /**
63
     * @inheritDoc
64
     *
65
     * @param string $id
66
     *
67
     * @return mixed|object
68
     * @psalm-suppress InvalidThrow
69
     */
70 54
    public function get($id)
71
    {
72 54
        if ($this->hasDefinition($id)) {
73 36
            return $this->build($id);
74
        }
75
76 21
        if ($this->hasInContainer($id)) {
77 14
            return $this->container->get($id);
78
        }
79
80 7
        throw new NotInstantiableClassException($id);
81
    }
82
83 3
    public function has($id): bool
84
    {
85 3
        return $this->hasDefinition($id) || $this->hasInContainer($id);
86
    }
87
88
    /**
89
     * @return mixed
90
     */
91 94
    public function create(DefinitionInterface $definition)
92
    {
93 94
        if ($definition instanceof ArrayDefinition) {
94 68
            $this->creatingIds[$definition->getClass()] = 1;
95
        }
96
97
        try {
98 94
            return $definition->resolve($this);
99
        } finally {
100 94
            if ($definition instanceof ArrayDefinition) {
101 94
                unset($this->creatingIds[$definition->getClass()]);
102
            }
103
        }
104
    }
105
106
    /**
107
     * Get definition by identifier provided.
108
     *
109
     * @throws InvalidConfigException
110
     */
111 72
    public function getDefinition(string $id): DefinitionInterface
112
    {
113 72
        if (!isset($this->definitionInstances[$id])) {
114 72
            if (isset($this->definitions[$id])) {
115 71
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
116 24
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
117
                }
118 64
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
119
            } else {
120 1
                throw new LogicException(
121 1
                    sprintf('No definition found for "%s".', $id)
122
                );
123
            }
124
        }
125
126 63
        return $this->definitionInstances[$id];
127
    }
128
129
    /**
130
     * Check if there is a definition with a given identifier.
131
     *
132
     * @param string $id Identifier to look for.
133
     *
134
     * @return bool If there is a definition with a given identifier.
135
     */
136 101
    public function hasDefinition(string $id): bool
137
    {
138 101
        return array_key_exists($id, $this->definitions);
139
    }
140
141
    /**
142
     * Set definition for a given identifier.
143
     *
144
     * @param string $id Identifier to set definition for.
145
     * @param mixed $definition Definition to set.
146
     */
147 71
    public function setDefinition(string $id, $definition): void
148
    {
149 71
        $this->definitions[$id] = $definition;
150 71
    }
151
152
    /**
153
     * @param string $id
154
     *
155
     * @throws CircularReferenceException
156
     * @throws InvalidConfigException
157
     * @throws NotFoundException
158
     * @throws NotInstantiableException
159
     *
160
     * @return mixed|object
161
     */
162 36
    private function build(string $id)
163
    {
164 36
        if (isset($this->creatingIds[$id])) {
165 5
            throw new CircularReferenceException(sprintf(
166 5
                'Circular reference to "%s" detected while creating: %s.',
167
                $id,
168 5
                implode(',', array_keys($this->creatingIds))
169
            ));
170
        }
171
172 35
        $definition = $this->getDefinition($id);
173
174 35
        $this->creatingIds[$id] = 1;
175
        try {
176 35
            return $definition->resolve($this);
177
        } finally {
178 35
            unset($this->creatingIds[$id]);
179
        }
180
    }
181
182
    /**
183
     * @psalm-assert ContainerInterface $this->container
184
     */
185 24
    private function hasInContainer(string $id): bool
186
    {
187 24
        return $this->container !== null && $this->container->has($id);
188
    }
189
}
190