Passed
Push — master ( e066b9...73e2c9 )
by Alexander
02:31
created

FactoryContainer::getDefinition()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 4
nop 1
dl 0
loc 15
ccs 8
cts 8
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use Psr\Container\NotFoundExceptionInterface;
10
use Yiisoft\Definitions\ArrayDefinition;
11
use Yiisoft\Definitions\Contract\DefinitionInterface;
12
use Yiisoft\Definitions\Contract\ReferenceInterface;
13
use Yiisoft\Definitions\Exception\CircularReferenceException;
14
use Yiisoft\Definitions\Exception\InvalidConfigException;
15
use Yiisoft\Definitions\Exception\NotFoundException;
16
use Yiisoft\Definitions\Exception\NotInstantiableClassException;
17
use Yiisoft\Definitions\Exception\NotInstantiableException;
18
use Yiisoft\Definitions\Infrastructure\Normalizer;
19
20
use function is_object;
21
22
/**
23
 * @internal
24
 */
25
final class FactoryContainer implements ContainerInterface
26
{
27
    private ?ContainerInterface $container;
28
29
    /**
30
     * @var mixed[] Definitions
31
     * @psalm-var array<string, mixed>
32
     */
33
    private array $definitions = [];
34
35
    /**
36
     * @var DefinitionInterface[] object definitions indexed by their types
37
     * @psalm-var array<string, DefinitionInterface>
38
     */
39
    private array $definitionInstances = [];
40
41
    /**
42
     * @var array used to collect IDs instantiated during build to detect circular references
43
     *
44
     * @psalm-var array<string,1>
45
     */
46
    private array $creatingIds = [];
47
48 88
    public function __construct(?ContainerInterface $container)
49
    {
50 88
        $this->container = $container;
51 88
    }
52
53
    /**
54
     * @param string $id
55
     *
56
     * @throws NotFoundExceptionInterface
57
     * @throws ContainerExceptionInterface
58
     *
59
     * @return mixed|object
60
     *
61
     * @psalm-suppress InvalidThrow
62
     */
63 62
    public function get($id)
64
    {
65 62
        if (isset($this->definitions[$id]) || class_exists($id)) {
66 62
            return $this->build($id);
67
        }
68
69 3
        throw new NotInstantiableClassException($id);
70
    }
71
72
    /**
73
     * @param string $id
74
     *
75
     * @return bool
76
     */
77
    public function has($id): bool
78
    {
79
        return isset($this->definitions[$id]) || ($this->container !== null && $this->container->has($id)) || class_exists($id);
80
    }
81
82
    /**
83
     * @throws InvalidConfigException
84
     */
85 76
    public function getDefinition(string $id): DefinitionInterface
86
    {
87 76
        if (!isset($this->definitionInstances[$id])) {
88 76
            if (isset($this->definitions[$id])) {
89 76
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
90 11
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
91
                }
92 72
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
93
            } else {
94
                /** @psalm-var class-string $id */
95 22
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
96
            }
97
        }
98
99 71
        return $this->definitionInstances[$id];
100
    }
101
102 78
    public function hasDefinition(string $id): bool
103
    {
104 78
        if (isset($this->definitions[$id])) {
105 52
            return true;
106
        }
107
108 30
        if (class_exists($id)) {
109 27
            $this->definitions[$id] = $id;
110 27
            return true;
111
        }
112
113 3
        return false;
114
    }
115
116
    /**
117
     * @param mixed $definition
118
     */
119 53
    public function setDefinition(string $id, $definition): void
120
    {
121 53
        $this->definitions[$id] = $definition;
122 53
    }
123
124
    /**
125
     * @param string $id
126
     *
127
     * @throws CircularReferenceException
128
     * @throws InvalidConfigException
129
     * @throws NotFoundException
130
     * @throws NotInstantiableException
131
     *
132
     * @return mixed|object
133
     */
134 62
    private function build(string $id)
135
    {
136 62
        if (isset($this->creatingIds[$id])) {
137 5
            throw new CircularReferenceException(sprintf(
138 5
                'Circular reference to "%s" detected while creating: %s.',
139
                $id,
140 5
                implode(',', array_keys($this->creatingIds))
141
            ));
142
        }
143
144 62
        $definition = $this->getDefinition($id);
145 60
        if ($definition instanceof ArrayDefinition) {
146 52
            $definition->setReferenceContainer($this);
147
        }
148 60
        $this->creatingIds[$id] = 1;
149
        try {
150 60
            $container = ($this->container === null || $definition instanceof ReferenceInterface) ? $this : $this->container;
151
            try {
152 60
                return $definition->resolve($container);
153 9
            } catch (NotFoundExceptionInterface $e) {
154 1
                if ($container === $this) {
155
                    throw $e;
156
                }
157
158 1
                return $definition->resolve($this);
159
            }
160
        } finally {
161 60
            unset($this->creatingIds[$id]);
162 60
            if ($definition instanceof ArrayDefinition) {
163 60
                $definition->setReferenceContainer(null);
164
            }
165
        }
166
    }
167
}
168