Completed
Pull Request — master (#96)
by Andreas
36:19 queued 21:24
created

Container::unprotect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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