Passed
Pull Request — master (#93)
by Sergei
02:00
created

Factory::get()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory;
6
7
use Psr\Container\ContainerInterface;
8
use Yiisoft\Factory\Definition\ArrayDefinition;
9
use Yiisoft\Factory\Definition\DefinitionInterface;
10
use Yiisoft\Factory\Definition\Normalizer;
11
use Yiisoft\Factory\Definition\DefinitionValidator;
12
use Yiisoft\Factory\Exception\InvalidConfigException;
13
use Yiisoft\Factory\Exception\NotInstantiableException;
14
use Yiisoft\Injector\Injector;
15
16
class Factory implements FactoryInterface
17
{
18
    /**
19
     * @var ContainerInterface|null Parent container.
20
     */
21
    private ?ContainerInterface $container = null;
22
23
    /**
24
     * @var mixed[] Definitions
25
     * @psalm-var array<string, mixed>
26
     */
27
    private array $definitions = [];
28
29
    /**
30
     * @var DefinitionInterface[] object definitions indexed by their types
31
     * @psalm-var array<string, DefinitionInterface>
32
     */
33
    private array $definitionInstances = [];
34
35
    /**
36
     * @var bool $validate Validate definitions when set
37
     */
38
    private bool $validate;
39
40
    /**
41
     * Factory constructor.
42
     *
43
     * @psalm-param array<string, mixed> $definitions
44
     *
45
     * @throws InvalidConfigException
46
     * @throws NotInstantiableException
47
     */
48 38
    public function __construct(
49
        ContainerInterface $container = null,
50
        array $definitions = [],
51
        bool $validate = true
52
    ) {
53 38
        $this->container = $container;
54 38
        $this->validate = $validate;
55 38
        $this->setDefaultDefinitions();
56 38
        $this->setMultiple($definitions);
57 38
    }
58
59 26
    public function create($config, array $constructorArguments = [])
60
    {
61 26
        if ($this->validate) {
62 26
            DefinitionValidator::validate($config);
63
        }
64
65 25
        $definition = Normalizer::normalize($config);
66 25
        if ($definition instanceof ArrayDefinition) {
67 18
            if (!empty($constructorArguments)) {
68 10
                $definition->mergeConstructorArguments($constructorArguments);
69
            }
70 18
            if ($this->has($definition->getClass())) {
71 4
                $definition = $this->merge(
72 4
                    $this->getDefinition($definition->getClass()),
73
                    $definition
74
                );
75
            }
76
        }
77
78 25
        if ($definition instanceof ArrayDefinition) {
79 18
            return $definition->resolve($this->container ?? $this);
80
        }
81
82 7
        return $definition->resolve($this);
83
    }
84
85 4
    private function merge(DefinitionInterface $one, ArrayDefinition $two): DefinitionInterface
86
    {
87 4
        return $one instanceof ArrayDefinition ? $one->merge($two) : $two;
88
    }
89
90
    /**
91
     * @param string $id
92
     *
93
     * @throws NotInstantiableException
94
     *
95
     * @return mixed|object
96
     */
97 18
    public function get($id)
98
    {
99
        try {
100 18
            $definition = $this->getDefinition($id);
101 1
        } catch (InvalidConfigException $e) {
102 1
            throw new NotInstantiableException($id);
103
        }
104
105 17
        if ($definition instanceof ArrayDefinition) {
106 15
            return $definition->resolve($this->container ?? $this);
107
        }
108
109 6
        return $definition->resolve($this);
110
    }
111
112
    /**
113
     * @throws InvalidConfigException
114
     */
115 22
    public function getDefinition(string $id): DefinitionInterface
116
    {
117 22
        if (!isset($this->definitionInstances[$id])) {
118 22
            if (isset($this->definitions[$id])) {
119 15
                $this->definitionInstances[$id] = Normalizer::normalize($this->definitions[$id], $id);
120
            } else {
121
                /** @psalm-var class-string $id */
122 11
                $this->definitionInstances[$id] = ArrayDefinition::fromPreparedData($id);
123
            }
124
        }
125
126 21
        return $this->definitionInstances[$id];
127
    }
128
129
    /**
130
     * Sets a definition to the factory.
131
     *
132
     * @param mixed $definition
133
     *
134
     * @throws InvalidConfigException
135
     */
136 38
    public function set(string $id, $definition): void
137
    {
138 38
        if ($this->validate) {
139 37
            DefinitionValidator::validate($definition, $id);
140
        }
141
142 38
        $this->definitions[$id] = $definition;
143 38
    }
144
145
    /**
146
     * Sets multiple definitions at once.
147
     *
148
     * @param array $definitions definitions indexed by their ids
149
     *
150
     * @psalm-param array<string, mixed> $definitions
151
     *
152
     * @throws InvalidConfigException
153
     */
154 38
    public function setMultiple(array $definitions): void
155
    {
156
        /** @var mixed $definition */
157 38
        foreach ($definitions as $id => $definition) {
158 38
            $this->set($id, $definition);
159
        }
160 38
    }
161
162
    /**
163
     * Returns a value indicating whether the container has the definition of the specified name.
164
     *
165
     * @param string $id class name, interface name or alias name
166
     *
167
     * @return bool whether the container is able to provide instance of class specified.
168
     *
169
     * @see set()
170
     */
171 18
    public function has($id): bool
172
    {
173 18
        return isset($this->definitions[$id]);
174
    }
175
176 38
    private function setDefaultDefinitions(): void
177
    {
178
        /** @var ContainerInterface */
179 38
        $container = $this->container ?? $this;
180
181 38
        $this->setMultiple([
182 38
            ContainerInterface::class => $container,
183 38
            Injector::class => new Injector($container),
184
        ]);
185 38
    }
186
}
187