Completed
Branch master (daf1a7)
by Anton
01:13
created

Container::addScalar()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php declare(strict_types=1);
2
3
namespace Phact\Container;
4
5
use Phact\Container\Builder\Builder;
6
use Phact\Container\Builder\BuilderInterface;
7
use Phact\Container\Definition\Definition;
8
use Phact\Container\Definition\DefinitionAggregate;
9
use Phact\Container\Definition\DefinitionAggregateInterface;
10
use Phact\Container\Definition\DefinitionInterface;
11
use Phact\Container\Delegate\Delegate;
12
use Phact\Container\Exceptions\CircularException;
13
use Phact\Container\Exceptions\DuplicateNameException;
14
use Phact\Container\Exceptions\NotFoundException;
15
use Phact\Container\Inflection\InflectionInterface;
16
use Psr\Container\ContainerInterface as PsrContainerInterface;
17
18
class Container implements ContainerInterface
19
{
20
    /**
21
     * @var DefinitionAggregate
22
     */
23
    protected $definitionAggregate;
24
25
    /**
26
     * @var array
27
     */
28
    protected $scalars = [];
29
30
    /**
31
     * Resolved shared instances
32
     *
33
     * @var array
34
     */
35
    protected $shared = [];
36
37
    /**
38
     * @var InflectionInterface[]
39
     */
40
    protected $inflections = [];
41
42
    /**
43
     * @var Delegate[]
44
     */
45
    protected $delegates = [];
46
47
    /**
48
     * Now loading services
49
     * @var array
50
     */
51
    protected $loading = [];
52
53
    /**
54
     * @var bool
55
     */
56
    protected $autoWire = true;
57
58
    /**
59
     * @var BuilderInterface
60
     */
61
    protected $builder;
62
63 25
    public function __construct(
64
        ?BuilderInterface $builder = null,
65
        ?DefinitionAggregateInterface $definitionAggregate = null
66
    ) {
67 25
        $this->builder = $builder ?: new Builder();
68 25
        $this->builder->setContainer($this);
69
70 25
        $this->definitionAggregate = $definitionAggregate ?: new DefinitionAggregate();
71 25
    }
72
73
    /**
74
     * {@inheritDoc}
75
     */
76 1
    public function addDefinitionClass(string $name, string $class): DefinitionInterface
77
    {
78 1
        return $this->definitionAggregate->addDefinition($name, new Definition($class));
79
    }
80
81
    /**
82
     * {@inheritDoc}
83
     */
84 12
    public function addDefinition(string $name, DefinitionInterface $definition): DefinitionInterface
85
    {
86 12
        return $this->definitionAggregate->addDefinition($name, $definition);
87
    }
88
89
    /**
90
     * {@inheritDoc}
91
     */
92 2
    public function addScalar(string $name, $value): void
93
    {
94 2
        $this->scalars[$name] = $value;
95 2
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100 1
    public function addReference(string $name, string $class): void
101
    {
102 1
        $this->definitionAggregate->addReference($name, $class);
103 1
    }
104
105
    /**
106
     * {@inheritDoc}
107
     */
108 1
    public function addAliases(string $name, array $aliases = []): void
109
    {
110 1
        $this->definitionAggregate->addAliases($name, $aliases);
111 1
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116 1
    public function addAlias(string $name, string $alias): void
117
    {
118 1
        $this->definitionAggregate->addAlias($name, $alias);
119 1
    }
120
121
    /**
122
     * {@inheritDoc}
123
     */
124 2
    public function addInflection(InflectionInterface $inflection): InflectionInterface
125
    {
126 2
        $this->inflections[] = $inflection;
127 2
        return $inflection;
128
    }
129
130
131
    /**
132
     * @inheritDoc
133
     */
134 2
    public function addDelegate(PsrContainerInterface $container, bool $applyInflection = true): void
135
    {
136 2
        $this->delegates[] = new Delegate($container, $applyInflection);
137 2
    }
138
139
    /**
140
     * Create object by DefinitionInterface object or retrieve it from shared
141
     *
142
     * @param DefinitionInterface $definition
143
     * @param string|null $id
144
     * @return object
145
     * @throws CircularException
146
     */
147 9
    protected function resolveDefinition(DefinitionInterface $definition, ?string $id = null): object
148
    {
149 9
        $this->setLoading($id);
150 9
        $object = $this->builder->construct($definition);
151 8
        if ($definition->isShared()) {
152 7
            $this->shared[$id] = $object;
153
        }
154 8
        $object = $this->builder->configure($object, $definition);
155 8
        $object = $this->inflect($object);
156 8
        $this->unsetLoading($id);
157 8
        return $object;
158
    }
159
160
    /**
161
     * Set that certain service are loading and checks circular exception
162
     *
163
     * @param string|null $id
164
     * @throws CircularException
165
     */
166 9
    protected function setLoading(?string $id): void
167
    {
168 9
        if ($id === null) {
169 2
            return;
170
        }
171 8
        if (isset($this->loading[$id])) {
172 1
            $loadingServices = implode(', ', array_keys($this->loading));
173 1
            throw new CircularException(sprintf("Circular dependency detected with services - %s", $loadingServices));
174
        }
175 8
        $this->loading[$id] = true;
176 8
    }
177
178
    /**
179
     * Unset that certain service are loading
180
     *
181
     * @param string|null $id
182
     */
183 8
    protected function unsetLoading(?string $id): void
184
    {
185 8
        if ($id === null) {
186 2
            return;
187
        }
188 7
        unset($this->loading[$id]);
189 7
    }
190
191
    /**
192
     * Apply all necessary inflections to object
193
     *
194
     * @param object $object
195
     * @return object
196
     */
197 9
    protected function inflect(object $object): object
198
    {
199 9
        foreach ($this->inflections as $inflection) {
200 2
            if ($inflection->canBeAppliedTo($object)) {
201 1
                $object = $this->builder->inflect($object, $inflection);
202
            }
203
        }
204 9
        return $object;
205
    }
206
207
    /**
208
     * @inheritDoc
209
     */
210 12
    public function get($id)
211
    {
212 12
        if (isset($this->scalars[$id])) {
213 1
            return $this->scalars[$id];
214
        }
215
216 11
        if ($this->definitionAggregate->has($id)) {
217 8
            return $this->getFromDefinitions($id);
218
        }
219
220 4
        if ($this->isAutoWireAvailiableForClass($id)) {
221 2
            return $this->resolveDefinition(
222 2
                (new Definition($id))->setShared(false)
223
            );
224
        }
225
226 2
        if ($resolved = $this->getFromDelegates($id)) {
227 1
            return $resolved;
228
        }
229
230 1
        throw new NotFoundException("Could not resolve element by id - {$id}");
231
    }
232
233
    /**
234
     * Check that autowire is enabled and class can be loaded
235
     *
236
     * @param string $class
237
     * @return bool
238
     */
239 7
    protected function isAutoWireAvailiableForClass(string $class): bool
240
    {
241 7
        return $this->autoWire && class_exists($class);
242
    }
243
244
    /**
245
     * Try to find entry in definitions or shared
246
     *
247
     * @param string $id
248
     * @return object
249
     * @throws CircularException
250
     * @throws NotFoundException
251
     */
252 8
    protected function getFromDefinitions(string $id): object
253
    {
254 8
        $id = $this->definitionAggregate->resolveDefinitionName($id);
255 8
        return $this->shared[$id] ?? $this->resolveDefinition($this->definitionAggregate->get($id), $id);
256
    }
257
258
    /**
259
     * Try to find entry in delegates
260
     *
261
     * @param $id
262
     * @return object|null
263
     */
264 2
    protected function getFromDelegates($id)
265
    {
266 2
        foreach ($this->delegates as $delegate) {
267 1
            if ($delegate->getContainer()->has($id)) {
268 1
                $resolved = $delegate->getContainer()->get($id);
269 1
                if ($delegate->isApplyInflection()) {
270 1
                    $resolved = $this->inflect($resolved);
271
                }
272 1
                return $resolved;
273
            }
274
        }
275 1
        return null;
276
    }
277
278
    /**
279
     * Check that entry exists in delegates
280
     *
281
     * @param $id
282
     * @return bool
283
     */
284 3
    protected function hasInDelegates($id): bool
285
    {
286 3
        foreach ($this->delegates as $delegate) {
287 1
            if ($delegate->getContainer()->has($id)) {
288 1
                return true;
289
            }
290
        }
291 2
        return false;
292
    }
293
294
    /**
295
     * @inheritDoc
296
     */
297 13
    public function has($id): bool
298
    {
299 13
        if (isset($this->scalars[$id])) {
300 2
            return true;
301
        }
302
303 11
        if ($this->definitionAggregate->has($id)) {
304 7
            return true;
305
        }
306
307 4
        if ($this->isAutoWireAvailiableForClass($id)) {
308 1
            return true;
309
        }
310
311 3
        return $this->hasInDelegates($id);
312
    }
313
314
    /**
315
     * @inheritDoc
316
     */
317 1
    public function invoke(callable $callable, array $arguments = [])
318
    {
319 1
        return $this->builder->invoke($callable, $arguments);
320
    }
321
322
    /**
323
     * Set that all dependencies will be analyzed for constructors, callable objects and methods
324
     *
325
     * @param bool $autoWire
326
     * @return $this
327
     */
328 1
    public function setAutoWire(bool $autoWire): self
329
    {
330 1
        $this->autoWire = $autoWire;
331 1
        return $this;
332
    }
333
}
334