Container::get()   C
last analyzed

Complexity

Conditions 20
Paths 38

Size

Total Lines 72
Code Lines 39

Duplication

Lines 19
Ratio 26.39 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 19
loc 72
rs 5.4314
cc 20
eloc 39
nc 38
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\DependencyInjection;
13
14
use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
15
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
17
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
18
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
19
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
20
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
21
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
22
23
/**
24
 * Container is a dependency injection container.
25
 *
26
 * It gives access to object instances (services).
27
 *
28
 * Services and parameters are simple key/pair stores.
29
 *
30
 * Parameter and service keys are case insensitive.
31
 *
32
 * A service id can contain lowercased letters, digits, underscores, and dots.
33
 * Underscores are used to separate words, and dots to group services
34
 * under namespaces:
35
 *
36
 * <ul>
37
 *   <li>request</li>
38
 *   <li>mysql_session_storage</li>
39
 *   <li>symfony.mysql_session_storage</li>
40
 * </ul>
41
 *
42
 * A service can also be defined by creating a method named
43
 * getXXXService(), where XXX is the camelized version of the id:
44
 *
45
 * <ul>
46
 *   <li>request -> getRequestService()</li>
47
 *   <li>mysql_session_storage -> getMysqlSessionStorageService()</li>
48
 *   <li>symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()</li>
49
 * </ul>
50
 *
51
 * The container can have three possible behaviors when a service does not exist:
52
 *
53
 *  * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default)
54
 *  * NULL_ON_INVALID_REFERENCE:      Returns null
55
 *  * IGNORE_ON_INVALID_REFERENCE:    Ignores the wrapping command asking for the reference
56
 *                                    (for instance, ignore a setter if the service does not exist)
57
 *
58
 * @author Fabien Potencier <[email protected]>
59
 * @author Johannes M. Schmitt <[email protected]>
60
 *
61
 * @api
62
 */
63
class Container implements IntrospectableContainerInterface
64
{
65
    /**
66
     * @var ParameterBagInterface
67
     */
68
    protected $parameterBag;
69
70
    protected $services = array();
71
    protected $methodMap = array();
72
    protected $aliases = array();
73
    protected $scopes = array();
74
    protected $scopeChildren = array();
75
    protected $scopedServices = array();
76
    protected $scopeStacks = array();
77
    protected $loading = array();
78
79
    private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_');
80
81
    /**
82
     * Constructor.
83
     *
84
     * @param ParameterBagInterface $parameterBag A ParameterBagInterface instance
85
     *
86
     * @api
87
     */
88
    public function __construct(ParameterBagInterface $parameterBag = null)
89
    {
90
        $this->parameterBag = $parameterBag ?: new ParameterBag();
91
    }
92
93
    /**
94
     * Compiles the container.
95
     *
96
     * This method does two things:
97
     *
98
     *  * Parameter values are resolved;
99
     *  * The parameter bag is frozen.
100
     *
101
     * @api
102
     */
103
    public function compile()
104
    {
105
        $this->parameterBag->resolve();
106
107
        $this->parameterBag = new FrozenParameterBag($this->parameterBag->all());
108
    }
109
110
    /**
111
     * Returns true if the container parameter bag are frozen.
112
     *
113
     * @return bool true if the container parameter bag are frozen, false otherwise
114
     *
115
     * @api
116
     */
117
    public function isFrozen()
118
    {
119
        return $this->parameterBag instanceof FrozenParameterBag;
120
    }
121
122
    /**
123
     * Gets the service container parameter bag.
124
     *
125
     * @return ParameterBagInterface A ParameterBagInterface instance
126
     *
127
     * @api
128
     */
129
    public function getParameterBag()
130
    {
131
        return $this->parameterBag;
132
    }
133
134
    /**
135
     * Gets a parameter.
136
     *
137
     * @param string $name The parameter name
138
     *
139
     * @return mixed The parameter value
140
     *
141
     * @throws InvalidArgumentException if the parameter is not defined
142
     *
143
     * @api
144
     */
145
    public function getParameter($name)
146
    {
147
        return $this->parameterBag->get($name);
148
    }
149
150
    /**
151
     * Checks if a parameter exists.
152
     *
153
     * @param string $name The parameter name
154
     *
155
     * @return bool The presence of parameter in container
156
     *
157
     * @api
158
     */
159
    public function hasParameter($name)
160
    {
161
        return $this->parameterBag->has($name);
162
    }
163
164
    /**
165
     * Sets a parameter.
166
     *
167
     * @param string $name  The parameter name
168
     * @param mixed  $value The parameter value
169
     *
170
     * @api
171
     */
172
    public function setParameter($name, $value)
173
    {
174
        $this->parameterBag->set($name, $value);
175
    }
176
177
    /**
178
     * Sets a service.
179
     *
180
     * Setting a service to null resets the service: has() returns false and get()
181
     * behaves in the same way as if the service was never created.
182
     *
183
     * @param string $id      The service identifier
184
     * @param object $service The service instance
185
     * @param string $scope   The scope of the service
186
     *
187
     * @throws RuntimeException         When trying to set a service in an inactive scope
188
     * @throws InvalidArgumentException When trying to set a service in the prototype scope
189
     *
190
     * @api
191
     */
192
    public function set($id, $service, $scope = self::SCOPE_CONTAINER)
193
    {
194
        if (self::SCOPE_PROTOTYPE === $scope) {
195
            throw new InvalidArgumentException(sprintf('You cannot set service "%s" of scope "prototype".', $id));
196
        }
197
198
        $id = strtolower($id);
199
200
        if ('service_container' === $id) {
201
            // BC: 'service_container' is no longer a self-reference but always
202
            // $this, so ignore this call.
203
            // @todo Throw InvalidArgumentException in next major release.
204
            return;
205
        }
206
        if (self::SCOPE_CONTAINER !== $scope) {
207
            if (!isset($this->scopedServices[$scope])) {
208
                throw new RuntimeException(sprintf('You cannot set service "%s" of inactive scope.', $id));
209
            }
210
211
            $this->scopedServices[$scope][$id] = $service;
212
        }
213
214
        $this->services[$id] = $service;
215
216
        if (method_exists($this, $method = 'synchronize'.strtr($id, $this->underscoreMap).'Service')) {
217
            $this->$method();
218
        }
219
220
        if (null === $service) {
221
            if (self::SCOPE_CONTAINER !== $scope) {
222
                unset($this->scopedServices[$scope][$id]);
223
            }
224
225
            unset($this->services[$id]);
226
        }
227
    }
228
229
    /**
230
     * Returns true if the given service is defined.
231
     *
232
     * @param string $id The service identifier
233
     *
234
     * @return bool true if the service is defined, false otherwise
235
     *
236
     * @api
237
     */
238
    public function has($id)
239
    {
240
        for ($i = 2;;) {
241 View Code Duplication
            if ('service_container' === $id
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242
                || isset($this->aliases[$id])
243
                || isset($this->services[$id])
244
                || array_key_exists($id, $this->services)
245
            ) {
246
                return true;
247
            }
248
            if (--$i && $id !== $lcId = strtolower($id)) {
249
                $id = $lcId;
250
            } else {
251
                return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service');
252
            }
253
        }
254
    }
255
256
    /**
257
     * Gets a service.
258
     *
259
     * If a service is defined both through a set() method and
260
     * with a get{$id}Service() method, the former has always precedence.
261
     *
262
     * @param string $id              The service identifier
263
     * @param int    $invalidBehavior The behavior when the service does not exist
264
     *
265
     * @return object The associated service
266
     *
267
     * @throws ServiceCircularReferenceException When a circular reference is detected
268
     * @throws ServiceNotFoundException          When the service is not defined
269
     * @throws \Exception                        if an exception has been thrown when the service has been resolved
270
     *
271
     * @see Reference
272
     *
273
     * @api
274
     */
275
    public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
276
    {
277
        // Attempt to retrieve the service by checking first aliases then
278
        // available services. Service IDs are case insensitive, however since
279
        // this method can be called thousands of times during a request, avoid
280
        // calling strtolower() unless necessary.
281
        for ($i = 2;;) {
282
            if ('service_container' === $id) {
283
                return $this;
284
            }
285
            if (isset($this->aliases[$id])) {
286
                $id = $this->aliases[$id];
287
            }
288
            // Re-use shared service instance if it exists.
289
            if (isset($this->services[$id]) || array_key_exists($id, $this->services)) {
290
                return $this->services[$id];
291
            }
292
293
            if (isset($this->loading[$id])) {
294
                throw new ServiceCircularReferenceException($id, array_keys($this->loading));
295
            }
296
297
            if (isset($this->methodMap[$id])) {
298
                $method = $this->methodMap[$id];
299
            } elseif (--$i && $id !== $lcId = strtolower($id)) {
300
                $id = $lcId;
301
                continue;
302
            } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) {
303
                // $method is set to the right value, proceed
304 View Code Duplication
            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
305
                if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
306
                    if (!$id) {
307
                        throw new ServiceNotFoundException($id);
308
                    }
309
310
                    $alternatives = array();
311
                    foreach ($this->services as $key => $associatedService) {
312
                        $lev = levenshtein($id, $key);
313
                        if ($lev <= strlen($id) / 3 || false !== strpos($key, $id)) {
314
                            $alternatives[] = $key;
315
                        }
316
                    }
317
318
                    throw new ServiceNotFoundException($id, null, null, $alternatives);
319
                }
320
321
                return;
322
            }
323
324
            $this->loading[$id] = true;
325
326
            try {
327
                $service = $this->$method();
328
            } catch (\Exception $e) {
329
                unset($this->loading[$id]);
330
331
                if (array_key_exists($id, $this->services)) {
332
                    unset($this->services[$id]);
333
                }
334
335
                if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
336
                    return;
337
                }
338
339
                throw $e;
340
            }
341
342
            unset($this->loading[$id]);
343
344
            return $service;
345
        }
346
    }
347
348
    /**
349
     * Returns true if the given service has actually been initialized.
350
     *
351
     * @param string $id The service identifier
352
     *
353
     * @return bool true if service has already been initialized, false otherwise
354
     */
355
    public function initialized($id)
356
    {
357
        $id = strtolower($id);
358
359
        if ('service_container' === $id) {
360
            // BC: 'service_container' was a synthetic service previously.
361
            // @todo Change to false in next major release.
362
            return true;
363
        }
364
365
        if (isset($this->aliases[$id])) {
366
            $id = $this->aliases[$id];
367
        }
368
369
        return isset($this->services[$id]) || array_key_exists($id, $this->services);
370
    }
371
372
    /**
373
     * Gets all service ids.
374
     *
375
     * @return array An array of all defined service ids
376
     */
377
    public function getServiceIds()
378
    {
379
        $ids = array();
380
        $r = new \ReflectionClass($this);
381
        foreach ($r->getMethods() as $method) {
382
            if (preg_match('/^get(.+)Service$/', $method->name, $match)) {
383
                $ids[] = self::underscore($match[1]);
384
            }
385
        }
386
        $ids[] = 'service_container';
387
388
        return array_unique(array_merge($ids, array_keys($this->services)));
389
    }
390
391
    /**
392
     * This is called when you enter a scope.
393
     *
394
     * @param string $name
395
     *
396
     * @throws RuntimeException         When the parent scope is inactive
397
     * @throws InvalidArgumentException When the scope does not exist
398
     *
399
     * @api
400
     */
401
    public function enterScope($name)
402
    {
403
        if (!isset($this->scopes[$name])) {
404
            throw new InvalidArgumentException(sprintf('The scope "%s" does not exist.', $name));
405
        }
406
407
        if (self::SCOPE_CONTAINER !== $this->scopes[$name] && !isset($this->scopedServices[$this->scopes[$name]])) {
408
            throw new RuntimeException(sprintf('The parent scope "%s" must be active when entering this scope.', $this->scopes[$name]));
409
        }
410
411
        // check if a scope of this name is already active, if so we need to
412
        // remove all services of this scope, and those of any of its child
413
        // scopes from the global services map
414
        if (isset($this->scopedServices[$name])) {
415
            $services = array($this->services, $name => $this->scopedServices[$name]);
416
            unset($this->scopedServices[$name]);
417
418 View Code Duplication
            foreach ($this->scopeChildren[$name] as $child) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
                if (isset($this->scopedServices[$child])) {
420
                    $services[$child] = $this->scopedServices[$child];
421
                    unset($this->scopedServices[$child]);
422
                }
423
            }
424
425
            // update global map
426
            $this->services = call_user_func_array('array_diff_key', $services);
0 ignored issues
show
Documentation Bug introduced by
It seems like call_user_func_array('array_diff_key', $services) of type * is incompatible with the declared type array of property $services.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
427
            array_shift($services);
428
429
            // add stack entry for this scope so we can restore the removed services later
430
            if (!isset($this->scopeStacks[$name])) {
431
                $this->scopeStacks[$name] = new \SplStack();
432
            }
433
            $this->scopeStacks[$name]->push($services);
434
        }
435
436
        $this->scopedServices[$name] = array();
437
    }
438
439
    /**
440
     * This is called to leave the current scope, and move back to the parent
441
     * scope.
442
     *
443
     * @param string $name The name of the scope to leave
444
     *
445
     * @throws InvalidArgumentException if the scope is not active
446
     *
447
     * @api
448
     */
449
    public function leaveScope($name)
450
    {
451
        if (!isset($this->scopedServices[$name])) {
452
            throw new InvalidArgumentException(sprintf('The scope "%s" is not active.', $name));
453
        }
454
455
        // remove all services of this scope, or any of its child scopes from
456
        // the global service map
457
        $services = array($this->services, $this->scopedServices[$name]);
458
        unset($this->scopedServices[$name]);
459
460 View Code Duplication
        foreach ($this->scopeChildren[$name] as $child) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
461
            if (isset($this->scopedServices[$child])) {
462
                $services[] = $this->scopedServices[$child];
463
                unset($this->scopedServices[$child]);
464
            }
465
        }
466
467
        // update global map
468
        $this->services = call_user_func_array('array_diff_key', $services);
0 ignored issues
show
Documentation Bug introduced by
It seems like call_user_func_array('array_diff_key', $services) of type * is incompatible with the declared type array of property $services.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
469
470
        // check if we need to restore services of a previous scope of this type
471
        if (isset($this->scopeStacks[$name]) && count($this->scopeStacks[$name]) > 0) {
472
            $services = $this->scopeStacks[$name]->pop();
473
            $this->scopedServices += $services;
474
475
            if ($this->scopeStacks[$name]->isEmpty()) {
476
                unset($this->scopeStacks[$name]);
477
            }
478
479
            foreach ($services as $array) {
480
                foreach ($array as $id => $service) {
481
                    $this->set($id, $service, $name);
482
                }
483
            }
484
        }
485
    }
486
487
    /**
488
     * Adds a scope to the container.
489
     *
490
     * @param ScopeInterface $scope
491
     *
492
     * @throws InvalidArgumentException
493
     *
494
     * @api
495
     */
496
    public function addScope(ScopeInterface $scope)
497
    {
498
        $name = $scope->getName();
499
        $parentScope = $scope->getParentName();
500
501
        if (self::SCOPE_CONTAINER === $name || self::SCOPE_PROTOTYPE === $name) {
502
            throw new InvalidArgumentException(sprintf('The scope "%s" is reserved.', $name));
503
        }
504
        if (isset($this->scopes[$name])) {
505
            throw new InvalidArgumentException(sprintf('A scope with name "%s" already exists.', $name));
506
        }
507
        if (self::SCOPE_CONTAINER !== $parentScope && !isset($this->scopes[$parentScope])) {
508
            throw new InvalidArgumentException(sprintf('The parent scope "%s" does not exist, or is invalid.', $parentScope));
509
        }
510
511
        $this->scopes[$name] = $parentScope;
512
        $this->scopeChildren[$name] = array();
513
514
        // normalize the child relations
515
        while ($parentScope !== self::SCOPE_CONTAINER) {
516
            $this->scopeChildren[$parentScope][] = $name;
517
            $parentScope = $this->scopes[$parentScope];
518
        }
519
    }
520
521
    /**
522
     * Returns whether this container has a certain scope.
523
     *
524
     * @param string $name The name of the scope
525
     *
526
     * @return bool
527
     *
528
     * @api
529
     */
530
    public function hasScope($name)
531
    {
532
        return isset($this->scopes[$name]);
533
    }
534
535
    /**
536
     * Returns whether this scope is currently active.
537
     *
538
     * This does not actually check if the passed scope actually exists.
539
     *
540
     * @param string $name
541
     *
542
     * @return bool
543
     *
544
     * @api
545
     */
546
    public function isScopeActive($name)
547
    {
548
        return isset($this->scopedServices[$name]);
549
    }
550
551
    /**
552
     * Camelizes a string.
553
     *
554
     * @param string $id A string to camelize
555
     *
556
     * @return string The camelized string
557
     */
558
    public static function camelize($id)
559
    {
560
        return strtr(ucwords(strtr($id, array('_' => ' ', '.' => '_ ', '\\' => '_ '))), array(' ' => ''));
561
    }
562
563
    /**
564
     * A string to underscore.
565
     *
566
     * @param string $id The string to underscore
567
     *
568
     * @return string The underscored string
569
     */
570
    public static function underscore($id)
571
    {
572
        return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($id, '_', '.')));
573
    }
574
}
575