Passed
Push — master ( 74f1c0...7c1546 )
by Alexander
12:27 queued 09:57
created

FactoryContainer::getDefinition()   B

Complexity

Conditions 10
Paths 8

Size

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