Completed
Push — master ( d4b5b7...255a3e )
by Anton
01:24
created

Container::addDelegate()   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 22
    public function __construct(
64
        ?BuilderInterface $builder = null,
65
        ?DefinitionAggregateInterface $definitionAggregate = null
66
    ) {
67 22
        if (!$builder) {
68 19
            $builder = new Builder();
69
        }
70 22
        $this->builder = $builder;
71 22
        $this->builder->setContainer($this);
72
73 22
        if (!$definitionAggregate) {
74 22
            $definitionAggregate = new DefinitionAggregate();
75
        }
76 22
        $this->definitionAggregate = $definitionAggregate;
77 22
    }
78
79
    /**
80
     * {@inheritDoc}
81
     */
82 1
    public function addDefinitionClass(string $name, string $class): DefinitionInterface
83
    {
84 1
        return $this->definitionAggregate->addDefinition($name, new Definition($class));
85
    }
86
87
    /**
88
     * {@inheritDoc}
89
     */
90 12
    public function addDefinition(string $name, DefinitionInterface $definition): DefinitionInterface
91
    {
92 12
        return $this->definitionAggregate->addDefinition($name, $definition);
93
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98 2
    public function addScalar(string $name, $value): void
99
    {
100 2
        $this->scalars[$name] = $value;
101 2
    }
102
103
    /**
104
     * {@inheritDoc}
105
     */
106
    public function addReference(string $name, string $class): void
107
    {
108
        $this->definitionAggregate->addReference($name, $class);
109
    }
110
111
    /**
112
     * {@inheritDoc}
113
     */
114
    public function addAliases(string $name, array $aliases = []): void
115
    {
116
        $this->definitionAggregate->addAliases($name, $aliases);
117
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122
    public function addAlias(string $name, string $alias): void
123
    {
124
        $this->definitionAggregate->addAlias($name, $alias);
125
    }
126
127
    /**
128
     * {@inheritDoc}
129
     */
130 2
    public function addInflection(InflectionInterface $inflection): InflectionInterface
131
    {
132 2
        $this->inflections[] = $inflection;
133 2
        return $inflection;
134
    }
135
136
    /**
137
     * Resolve definition by provided id from definitions, aliases and references
138
     *
139
     * @param string $id
140
     * @return object
141
     * @throws CircularException
142
     * @throws NotFoundException
143
     */
144 8
    protected function resolveDefinitionById(string $id): object
145
    {
146 8
        return $this->resolveDefinition($this->definitionAggregate->get($id), $id);
147
    }
148
149
    /**
150
     * Create object by DefinitionInterface object or retrieve it from shared
151
     *
152
     * @param DefinitionInterface $definition
153
     * @param string|null $id
154
     * @return object
155
     * @throws CircularException
156
     */
157 9
    protected function resolveDefinition(DefinitionInterface $definition, ?string $id = null): object
158
    {
159 9
        $this->setLoading($id);
160 9
        $object = $this->builder->construct($definition);
161 8
        if ($id !== null && $definition->isShared()) {
162 7
            $this->shared[$id] = $object;
163
        }
164 8
        $object = $this->builder->configure($object, $definition);
165 8
        $object = $this->inflect($object);
166 8
        $this->unsetLoading($id);
167 8
        return $object;
168
    }
169
170
    /**
171
     * Set that certain service are loading and checks circular exception
172
     *
173
     * @param string|null $id
174
     * @throws CircularException
175
     */
176 9
    protected function setLoading(?string $id): void
177
    {
178 9
        if ($id === null) {
179 2
            return;
180
        }
181 8
        if (isset($this->loading[$id])) {
182 1
            $loadingServices = implode(', ', array_keys($this->loading));
183 1
            throw new CircularException(sprintf("Circular dependency detected with services - %s", $loadingServices));
184
        }
185 8
        $this->loading[$id] = true;
186 8
    }
187
188
    /**
189
     * Unset that certain service are loading
190
     *
191
     * @param string|null $id
192
     */
193 8
    protected function unsetLoading(?string $id): void
194
    {
195 8
        if ($id === null) {
196 2
            return;
197
        }
198 7
        unset($this->loading[$id]);
199 7
    }
200
201
    /**
202
     * Apply all necessary inflections to object
203
     *
204
     * @param object $object
205
     * @return object
206
     */
207 9
    protected function inflect(object $object): object
208
    {
209 9
        foreach ($this->inflections as $inflection) {
210 2
            if ($inflection->canBeAppliedTo($object)) {
211 1
                $object = $this->builder->inflect($object, $inflection);
212
            }
213
        }
214 9
        return $object;
215
    }
216
217
    /**
218
     * @inheritDoc
219
     */
220 12
    public function get($id)
221
    {
222 12
        if (isset($this->scalars[$id])) {
223 1
            return $this->scalars[$id];
224
        }
225
226 11
        if ($this->definitionAggregate->has($id)) {
227 8
            $id = $this->definitionAggregate->resolveDefinitionName($id);
228 8
            return $this->shared[$id] ?? $this->resolveDefinitionById($id);
229
        }
230
231 4
        if ($this->autoWire && class_exists($id)) {
232 2
            return $this->resolveDefinition(
233 2
                (new Definition($id))->setShared(true)
234
            );
235
        }
236
237 2
        if ($resolved = $this->getFromDelegates($id)) {
238 1
            return $resolved;
239
        }
240
241 1
        throw new NotFoundException("Could not resolve element by id - {$id}");
242
    }
243
244
    /**
245
     * Try to find entry in delegates
246
     *
247
     * @param $id
248
     * @return mixed|object|null
249
     */
250 2
    public function getFromDelegates($id)
251
    {
252 2
        foreach ($this->delegates as $delegate) {
253 1
            if ($delegate->getContainer()->has($id)) {
254 1
                $resolved = $delegate->getContainer()->get($id);
255 1
                if ($delegate->isApplyInflection()) {
256 1
                    $resolved = $this->inflect($resolved);
257
                }
258 1
                return $resolved;
259
            }
260
        }
261 1
        return null;
262
    }
263
264
    /**
265
     * @inheritDoc
266
     */
267 13
    public function has($id): bool
268
    {
269 13
        if (isset($this->shared[$id])) {
270 1
            return true;
271
        }
272
273 12
        if (isset($this->scalars[$id])) {
274 2
            return true;
275
        }
276
277 10
        if ($this->definitionAggregate->has($id)) {
278 6
            return true;
279
        }
280
281 4
        if ($this->autoWire && class_exists($id)) {
282 1
            return true;
283
        }
284
285 3
        foreach ($this->delegates as $delegate) {
286 1
            if ($delegate->getContainer()->has($id)) {
287 1
                return true;
288
            }
289
        }
290
291 2
        return false;
292
    }
293
294
    /**
295
     * @inheritDoc
296
     */
297 1
    public function invoke(callable $callable, array $arguments = [])
298
    {
299 1
        return $this->builder->invoke($callable, $arguments);
300
    }
301
302
    /**
303
     * @inheritDoc
304
     */
305 2
    public function addDelegate(PsrContainerInterface $container, bool $applyInflection = true): void
306
    {
307 2
        $this->delegates[] = new Delegate($container, $applyInflection);
308 2
    }
309
310
    /**
311
     * Set that all dependencies will be analyzed for constructors, callable objects and methods
312
     *
313
     * @param bool $autoWire
314
     * @return $this
315
     */
316 1
    public function setAutoWire(bool $autoWire): self
317
    {
318 1
        $this->autoWire = $autoWire;
319 1
        return $this;
320
    }
321
}
322