Passed
Pull Request — master (#146)
by Dmitriy
11:01
created

FactoryContainer::build()   B

Complexity

Conditions 8
Paths 81

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8.0109

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 21
nc 81
nop 1
dl 0
loc 30
ccs 17
cts 18
cp 0.9444
crap 8.0109
rs 8.4444
c 1
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 86
    public function __construct(?ContainerInterface $container)
49
    {
50 86
        $this->container = $container;
51 86
    }
52
53
    /**
54
     * @throws NotFoundExceptionInterface
55
     * @throws ContainerExceptionInterface
56
     *
57
     * @return mixed|object
58
     *
59
     * @psalm-suppress InvalidThrow
60
     */
61 60
    public function get($id)
62
    {
63 60
        if (isset($this->definitions[$id]) || class_exists($id)) {
64 60
            return $this->build($id);
65
        }
66
67 3
        throw new NotInstantiableClassException($id);
68
    }
69
70
    public function has($id): bool
71
    {
72
        return isset($this->definitions[$id]) || ($this->container !== null && $this->container->has($id)) || class_exists($id);
73
    }
74
75
    /**
76
     * @throws InvalidConfigException
77
     */
78 74
    public function getDefinition(string $id): DefinitionInterface
79
    {
80 74
        if (!isset($this->definitionInstances[$id])) {
81 74
            if (isset($this->definitions[$id])) {
82 51
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
83 9
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
84
                }
85 47
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
86
            } else {
87
                /** @psalm-var class-string $id */
88 43
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
89
            }
90
        }
91
92 69
        return $this->definitionInstances[$id];
93
    }
94
95 76
    public function hasDefinition(string $id): bool
96
    {
97 76
        return isset($this->definitions[$id]) || class_exists($id);
98
    }
99
100
    /**
101
     * @param mixed $definition
102
     */
103 51
    public function setDefinition(string $id, $definition): void
104
    {
105 51
        $this->definitions[$id] = $definition;
106 51
    }
107
108
109
110
    /**
111
     * @param string $id
112
     *
113
     * @throws CircularReferenceException
114
     * @throws InvalidConfigException
115
     * @throws NotFoundException
116
     * @throws NotInstantiableException
117
     *
118
     * @return mixed|object
119
     */
120 60
    private function build(string $id)
121
    {
122 60
        if (isset($this->creatingIds[$id])) {
123 5
            throw new CircularReferenceException(sprintf(
124 5
                'Circular reference to "%s" detected while creating: %s.',
125
                $id,
126 5
                implode(',', array_keys($this->creatingIds))
127
            ));
128
        }
129
130 60
        $definition = $this->getDefinition($id);
131 58
        if ($definition instanceof ArrayDefinition) {
132 52
            $definition->setReferenceContainer($this);
133
        }
134 58
        $this->creatingIds[$id] = 1;
135
        try {
136 58
            $container = ($this->container === null || $definition instanceof ReferenceInterface) ? $this : $this->container;
137
            try {
138 58
                return $definition->resolve($container);
139 9
            } catch (NotFoundExceptionInterface $e) {
140 1
                if ($container === $this) {
141
                    throw $e;
142
                }
143
144 1
                return $definition->resolve($this);
145
            }
146
        } finally {
147 58
            unset($this->creatingIds[$id]);
148 58
            if ($definition instanceof ArrayDefinition) {
149 58
                $definition->setReferenceContainer(null);
150
            }
151
        }
152
    }
153
}
154