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

FactoryContainer::setDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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