Passed
Pull Request — master (#146)
by Dmitriy
02:20
created

FactoryContainer   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Test Coverage

Coverage 92.5%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 24
eloc 40
c 3
b 0
f 0
dl 0
loc 130
ccs 37
cts 40
cp 0.925
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A hasDefinition() 0 3 2
B build() 0 30 8
A getDefinition() 0 15 5
A setDefinition() 0 3 1
A get() 0 7 3
A has() 0 3 4
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
     * @param string $id
55
     *
56
     * @throws NotFoundExceptionInterface
57
     * @throws ContainerExceptionInterface
58
     *
59
     * @return mixed|object
60
     *
61
     * @psalm-suppress InvalidThrow
62
     */
63 60
    public function get($id)
64
    {
65 60
        if (isset($this->definitions[$id]) || class_exists($id)) {
66 60
            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 74
    public function getDefinition(string $id): DefinitionInterface
86
    {
87 74
        if (!isset($this->definitionInstances[$id])) {
88 74
            if (isset($this->definitions[$id])) {
89 51
                if (is_object($this->definitions[$id]) && !($this->definitions[$id] instanceof ReferenceInterface)) {
90 9
                    return Normalizer::normalize(clone $this->definitions[$id], $id);
91
                }
92 47
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
93
            } else {
94
                /** @psalm-var class-string $id */
95 43
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
96
            }
97
        }
98
99 69
        return $this->definitionInstances[$id];
100
    }
101
102 76
    public function hasDefinition(string $id): bool
103
    {
104 76
        return isset($this->definitions[$id]) || class_exists($id);
105
    }
106
107
    /**
108
     * @param mixed $definition
109
     */
110 51
    public function setDefinition(string $id, $definition): void
111
    {
112 51
        $this->definitions[$id] = $definition;
113 51
    }
114
115
    /**
116
     * @param string $id
117
     *
118
     * @throws CircularReferenceException
119
     * @throws InvalidConfigException
120
     * @throws NotFoundException
121
     * @throws NotInstantiableException
122
     *
123
     * @return mixed|object
124
     */
125 60
    private function build(string $id)
126
    {
127 60
        if (isset($this->creatingIds[$id])) {
128 5
            throw new CircularReferenceException(sprintf(
129 5
                'Circular reference to "%s" detected while creating: %s.',
130
                $id,
131 5
                implode(',', array_keys($this->creatingIds))
132
            ));
133
        }
134
135 60
        $definition = $this->getDefinition($id);
136 58
        if ($definition instanceof ArrayDefinition) {
137 52
            $definition->setReferenceContainer($this);
138
        }
139 58
        $this->creatingIds[$id] = 1;
140
        try {
141 58
            $container = ($this->container === null || $definition instanceof ReferenceInterface) ? $this : $this->container;
142
            try {
143 58
                return $definition->resolve($container);
144 9
            } catch (NotFoundExceptionInterface $e) {
145 1
                if ($container === $this) {
146
                    throw $e;
147
                }
148
149 1
                return $definition->resolve($this);
150
            }
151
        } finally {
152 58
            unset($this->creatingIds[$id]);
153 58
            if ($definition instanceof ArrayDefinition) {
154 58
                $definition->setReferenceContainer(null);
155
            }
156
        }
157
    }
158
}
159