Passed
Pull Request — master (#156)
by Alexander
02:08
created

FactoryInternalContainer::hasDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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