Passed
Pull Request — master (#151)
by Sergei
03:17 queued 01:15
created

FactoryContainer::getDefinition()   B

Complexity

Conditions 10
Paths 5

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 18
nc 5
nop 1
dl 0
loc 30
ccs 16
cts 16
cp 1
crap 10
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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