Completed
Pull Request — master (#96)
by Andreas
14:21 queued 12:14
created

Container::add()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 9
Bugs 2 Features 1
Metric Value
c 9
b 2
f 1
dl 0
loc 25
ccs 15
cts 15
cp 1
rs 8.439
cc 6
eloc 13
nc 8
nop 3
crap 6
1
<?php
2
3
namespace League\Container;
4
5
use Interop\Container\ContainerInterface as InteropContainerInterface;
6
use League\Container\Definition\DefinitionFactory;
7
use League\Container\Definition\DefinitionFactoryInterface;
8
use League\Container\Definition\DefinitionInterface;
9
use League\Container\Exception\ProtectedException;
10
use League\Container\Exception\NotFoundException;
11
use League\Container\Inflector\InflectorAggregate;
12
use League\Container\Inflector\InflectorAggregateInterface;
13
use League\Container\ServiceProvider\ServiceProviderAggregate;
14
use League\Container\ServiceProvider\ServiceProviderAggregateInterface;
15
16
class Container implements ContainerInterface
17
{
18
    /**
19
     * @var \League\Container\Definition\DefinitionFactoryInterface
20
     */
21
    protected $definitionFactory;
22
23
    /**
24
     * @var \League\Container\Definition\DefinitionInterface[]
25
     */
26
    protected $definitions = [];
27
28
    /**
29
     * @var \League\Container\Definition\DefinitionInterface[]
30
     */
31
    protected $sharedDefinitions = [];
32
33
    /**
34
     * @var \League\Container\Inflector\InflectorAggregateInterface
35
     */
36
    protected $inflectors;
37
38
    /**
39
     * @var \League\Container\ServiceProvider\ServiceProviderAggregateInterface
40
     */
41
    protected $providers;
42
43
    /**
44
     * @var array
45
     */
46
    protected $shared = [];
47
48
    /**
49
     * @var \Interop\Container\ContainerInterface[]
50
     */
51
    protected $delegates = [];
52
53
    /**
54
     * @var bool
55
     */
56
    protected $isProtected = false;
57
58
    /**
59
     * Constructor.
60
     *
61
     * @param \League\Container\ServiceProvider\ServiceProviderAggregateInterface|null $providers
62
     * @param \League\Container\Inflector\InflectorAggregateInterface|null             $inflectors
63
     * @param \League\Container\Definition\DefinitionFactoryInterface|null             $definitionFactory
64
     */
65 60
    public function __construct(
66
        ServiceProviderAggregateInterface $providers         = null,
67
        InflectorAggregateInterface       $inflectors        = null,
68
        DefinitionFactoryInterface        $definitionFactory = null
69
    ) {
70
        // set required dependencies
71 60
        $this->providers         = (is_null($providers))
72 60
                                 ? (new ServiceProviderAggregate)->setContainer($this)
73 60
                                 : $providers->setContainer($this);
74
75 60
        $this->inflectors        = (is_null($inflectors))
76 60
                                 ? (new InflectorAggregate)->setContainer($this)
77 60
                                 : $inflectors->setContainer($this);
78
79 60
        $this->definitionFactory = (is_null($definitionFactory))
80 60
                                 ? (new DefinitionFactory)->setContainer($this)
81 60
                                 : $definitionFactory->setContainer($this);
82 60
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 33
    public function get($alias, array $args = [])
88
    {
89 33
        $service = $this->getFromThisContainer($alias, $args);
90
91 33
        if ($service === false && $this->providers->provides($alias)) {
92 12
            $this->providers->register($alias);
93 12
            $service = $this->getFromThisContainer($alias, $args);
94 12
        }
95
96 33
        if ($service !== false) {
97 27
            return $service;
98
        }
99
100 12
        if ($resolved = $this->getFromDelegate($alias, $args)) {
101 6
            return $this->inflectors->inflect($resolved);
102
        }
103
104 6
        throw new NotFoundException(
105 6
            sprintf('Alias (%s) is not being managed by the container', $alias)
106 6
        );
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112 21
    public function has($alias)
113
    {
114 21
        if (array_key_exists($alias, $this->definitions) || $this->hasShared($alias)) {
115 15
            return true;
116
        }
117
118 9
        if ($this->providers->provides($alias)) {
119 3
            return true;
120
        }
121
122 9
        return $this->hasInDelegate($alias);
123
    }
124
125
    /**
126
     * Returns a boolean to determine if the container has a shared instance of an alias.
127
     *
128
     * @param  string  $alias
129
     * @param  boolean $resolved
130
     * @return boolean
131
     */
132 42
    public function hasShared($alias, $resolved = false)
133
    {
134 42
        $shared = ($resolved === false) ? array_merge($this->shared, $this->sharedDefinitions) : $this->shared;
135
136 42
        return (array_key_exists($alias, $shared));
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 36
    public function add($alias, $concrete = null, $share = false)
143
    {
144 36
        if (is_null($concrete)) {
145 3
            $concrete = $alias;
146 3
        }
147
148 36
        if ($this->isProtected() && $this->has($alias)) {
149 3
            throw new ProtectedException('Container has been protected, overriding services is not allowed.');
150
        }
151
152 36
        $definition = $this->definitionFactory->getDefinition($alias, $concrete);
153
154 36
        if ($definition instanceof DefinitionInterface) {
155 27
            if ($share === false) {
156 9
                $this->definitions[$alias] = $definition;
157 9
            } else {
158 21
                $this->sharedDefinitions[$alias] = $definition;
159
            }
160
161 27
            return $definition;
162
        }
163
164
        // dealing with a value that cannot build a definition
165 9
        $this->shared[$alias] = $concrete;
166 9
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 27
    public function share($alias, $concrete = null)
172
    {
173 27
        return $this->add($alias, $concrete, true);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 15
    public function addServiceProvider($provider)
180
    {
181 15
        if ($this->isProtected()) {
182 3
            throw new ProtectedException('Container has been protected, adding service providers is not allowed');
183
        }
184
185 12
        $this->providers->add($provider);
186
187 12
        return $this;
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193 6
    public function extend($alias)
194
    {
195 6
        if ($this->providers->provides($alias)) {
196 3
            $this->providers->register($alias);
197 3
        }
198
199 6
        if (array_key_exists($alias, $this->definitions)) {
200 3
            return $this->definitions[$alias];
201
        }
202
203 6
        if (array_key_exists($alias, $this->sharedDefinitions)) {
204 3
            return $this->sharedDefinitions[$alias];
205
        }
206
207 3
        throw new NotFoundException(
208 3
            sprintf('Unable to extend alias (%s) as it is not being managed as a definition', $alias)
209 3
        );
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 3
    public function inflector($type, callable $callback = null)
216
    {
217 3
        if ($this->isProtected()) {
218 3
            throw new ProtectedException('Container has been protected, adding inflectors is not allowed');
219
        }
220
221
        return $this->inflectors->add($type, $callback);
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    public function call(callable $callable, array $args = [])
228
    {
229
        return (new ReflectionContainer)->setContainer($this)->call($callable, $args);
230
    }
231
232
    /**
233
     * Marks the container as protected.
234
     *
235
     * Prevents overriding services and delegating to other containers once the container is marked as protected.
236
     */
237 15
    public function protect()
238
    {
239 15
        $this->isProtected = true;
240 15
    }
241
242
    /**
243
     * Marks the container as unprotected.
244
     *
245
     * Allows overriding services and delegating to other containers (this is the default).
246
     */
247 3
    public function unprotect()
248
    {
249 3
        $this->isProtected = false;
250 3
    }
251
252
    /**
253
     * Returns true if the container is protected.
254
     *
255
     * @return bool
256
     */
257 54
    public function isProtected()
258
    {
259 54
        return $this->isProtected;
260
    }
261
262
    /**
263
     * Delegate a backup container to be checked for services if it
264
     * cannot be resolved via this container.
265
     *
266
     * @param  \Interop\Container\ContainerInterface $container
267
     * @return $this
268
     */
269 12
    public function delegate(InteropContainerInterface $container)
270
    {
271 12
        if ($this->isProtected()) {
272 3
            throw new ProtectedException('Container has been protected, delegating to additional containers is not allowed');
273
        }
274
275 9
        $this->delegates[] = $container;
276
277 9
        if ($container instanceof ImmutableContainerAwareInterface) {
278 3
            $container->setContainer($this);
279 3
        }
280
281 9
        return $this;
282
    }
283
284
    /**
285
     * Returns true if service is registered in one of the delegated backup containers.
286
     *
287
     * @param  string $alias
288
     * @return boolean
289
     */
290 9
    public function hasInDelegate($alias)
291
    {
292 9
        foreach ($this->delegates as $container) {
293 3
            if ($container->has($alias)) {
294 3
                return true;
295
            }
296 9
        }
297
298 6
        return false;
299
    }
300
301
    /**
302
     * Attempt to get a service from the stack of delegated backup containers.
303
     *
304
     * @param  string $alias
305
     * @param  array  $args
306
     * @return mixed
307
     */
308 12
    protected function getFromDelegate($alias, array $args = [])
309
    {
310 12
        foreach ($this->delegates as $container) {
311 6
            if ($container->has($alias)) {
312 6
                return $container->get($alias, $args);
313
            }
314
315 3
            continue;
316 6
        }
317
318 6
        return false;
319
    }
320
321
    /**
322
     * Get a service that has been registered in this container.
323
     *
324
     * @param  string $alias
325
     * @param  array $args
326
     * @return mixed
327
     */
328 33
    protected function getFromThisContainer($alias, array $args = [])
329
    {
330 33
        if ($this->hasShared($alias, true)) {
331 12
            return $this->inflectors->inflect($this->shared[$alias]);
332
        }
333
334 27
        if (array_key_exists($alias, $this->sharedDefinitions)) {
335 15
            $shared = $this->inflectors->inflect($this->sharedDefinitions[$alias]->build());
336 15
            $this->shared[$alias] = $shared;
337 15
            return $shared;
338
        }
339
340 24
        if (array_key_exists($alias, $this->definitions)) {
341 6
            return $this->inflectors->inflect(
342 6
                $this->definitions[$alias]->build($args)
343 6
            );
344
        }
345
346 21
        return false;
347
    }
348
}
349