Container::createService()   F
last analyzed

Complexity

Conditions 37
Paths > 20000

Size

Total Lines 134
Code Lines 84

Duplication

Lines 114
Ratio 85.07 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 114
loc 134
rs 2
c 1
b 0
f 0
cc 37
eloc 84
nc 40510
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
 * @file
5
 * Contains \Drupal\Component\DependencyInjection\Container.
6
 */
7
8
namespace Drupal\Component\DependencyInjection;
9
10
use Symfony\Component\DependencyInjection\ContainerInterface;
11
use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
12
use Symfony\Component\DependencyInjection\ScopeInterface;
13
use Symfony\Component\DependencyInjection\Exception\LogicException;
14
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
15
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
16
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
17
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
18
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
19
20
/**
21
 * Provides a container optimized for Drupal's needs.
22
 *
23
 * This container implementation is compatible with the default Symfony
24
 * dependency injection container and similar to the Symfony ContainerBuilder
25
 * class, but optimized for speed.
26
 *
27
 * It is based on a PHP array container definition dumped as a
28
 * performance-optimized machine-readable format.
29
 *
30
 * The best way to initialize this container is to use a Container Builder,
31
 * compile it and then retrieve the definition via
32
 * \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper::getArray().
33
 *
34
 * The retrieved array can be cached safely and then passed to this container
35
 * via the constructor.
36
 *
37
 * As the container is unfrozen by default, a second parameter can be passed to
38
 * the container to "freeze" the parameter bag.
39
 *
40
 * This container is different in behavior from the default Symfony container in
41
 * the following ways:
42
 *
43
 * - It only allows lowercase service and parameter names, though it does only
44
 *   enforce it via assertions for performance reasons.
45
 * - The following functions, that are not part of the interface, are explicitly
46
 *   not supported: getParameterBag(), isFrozen(), compile(),
47
 *   getAServiceWithAnIdByCamelCase().
48
 * - The function getServiceIds() was added as it has a use-case in core and
49
 *   contrib.
50
 * - Scopes are explicitly not allowed, because Symfony 2.8 has deprecated
51
 *   them and they will be removed in Symfony 3.0.
52
 * - Synchronized services are explicitly not supported, because Symfony 2.8 has
53
 *   deprecated them and they will be removed in Symfony 3.0.
54
 *
55
 * @ingroup container
56
 */
57
class Container implements IntrospectableContainerInterface {
58
59
  /**
60
   * The parameters of the container.
61
   *
62
   * @var array
63
   */
64
  protected $parameters = array();
65
66
  /**
67
   * The aliases of the container.
68
   *
69
   * @var array
70
   */
71
  protected $aliases = array();
72
73
  /**
74
   * The service definitions of the container.
75
   *
76
   * @var array
77
   */
78
  protected $serviceDefinitions = array();
79
80
  /**
81
   * The instantiated services.
82
   *
83
   * @var array
84
   */
85
  protected $services = array();
86
87
  /**
88
   * The instantiated private services.
89
   *
90
   * @var array
91
   */
92
  protected $privateServices = array();
93
94
  /**
95
   * The currently loading services.
96
   *
97
   * @var array
98
   */
99
  protected $loading = array();
100
101
  /**
102
   * Whether the container parameters can still be changed.
103
   *
104
   * For testing purposes the container needs to be changed.
105
   *
106
   * @var bool
107
   */
108
  protected $frozen = TRUE;
109
110
  /**
111
   * Constructs a new Container instance.
112
   *
113
   * @param array $container_definition
114
   *   An array containing the following keys:
115
   *   - aliases: The aliases of the container.
116
   *   - parameters: The parameters of the container.
117
   *   - services: The service definitions of the container.
118
   *   - frozen: Whether the container definition came from a frozen
119
   *     container builder or not.
120
   *   - machine_format: Whether this container definition uses the optimized
121
   *     machine-readable container format.
122
   */
123 View Code Duplication
  public function __construct(array $container_definition = array()) {
124
    if (!empty($container_definition) && (!isset($container_definition['machine_format']) || $container_definition['machine_format'] !== TRUE)) {
125
      throw new InvalidArgumentException('The non-optimized format is not supported by this class. Use an optimized machine-readable format instead, e.g. as produced by \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.');
126
    }
127
128
    $this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array();
129
    $this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : array();
130
    $this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : array();
131
    $this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE;
132
133
    // Register the service_container with itself.
134
    $this->services['service_container'] = $this;
135
  }
136
137
  /**
138
   * {@inheritdoc}
139
   */
140
  public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
141
    if (isset($this->aliases[$id])) {
142
      $id = $this->aliases[$id];
143
    }
144
145
    // Re-use shared service instance if it exists.
146 View Code Duplication
    if (isset($this->services[$id]) || ($invalid_behavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($id, $this->services))) {
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...
147
      return $this->services[$id];
148
    }
149
150
    if (isset($this->loading[$id])) {
151
      throw new ServiceCircularReferenceException($id, array_keys($this->loading));
152
    }
153
154
    $definition = isset($this->serviceDefinitions[$id]) ? $this->serviceDefinitions[$id] : NULL;
155
156
    if (!$definition && $invalid_behavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
157
      if (!$id) {
158
        throw new ServiceNotFoundException($id);
159
      }
160
161
      throw new ServiceNotFoundException($id, NULL, NULL, $this->getServiceAlternatives($id));
162
    }
163
164
    // In case something else than ContainerInterface::NULL_ON_INVALID_REFERENCE
165
    // is used, the actual wanted behavior is to re-try getting the service at a
166
    // later point.
167
    if (!$definition) {
168
      return;
169
    }
170
171
    // Definition is a keyed array, so [0] is only defined when it is a
172
    // serialized string.
173
    if (isset($definition[0])) {
174
      $definition = unserialize($definition);
175
    }
176
177
    // Now create the service.
178
    $this->loading[$id] = TRUE;
179
180
    try {
181
      $service = $this->createService($definition, $id);
182
    }
183
    catch (\Exception $e) {
184
      unset($this->loading[$id]);
185
186
      // Remove a potentially shared service that was constructed incompletely.
187
      if (array_key_exists($id, $this->services)) {
188
        unset($this->services[$id]);
189
      }
190
191
      if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) {
192
        return;
193
      }
194
195
      throw $e;
196
    }
197
198
    unset($this->loading[$id]);
199
200
    return $service;
201
  }
202
203
  /**
204
   * Creates a service from a service definition.
205
   *
206
   * @param array $definition
207
   *   The service definition to create a service from.
208
   * @param string $id
209
   *   The service identifier, necessary so it can be shared if its public.
210
   *
211
   * @return object
212
   *   The service described by the service definition.
213
   *
214
   * @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException
215
   *   Thrown when the service is a synthetic service.
216
   * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
217
   *   Thrown when the configurator callable in $definition['configurator'] is
218
   *   not actually a callable.
219
   * @throws \ReflectionException
220
   *   Thrown when the service class takes more than 10 parameters to construct,
221
   *   and cannot be instantiated.
222
   */
223
  protected function createService(array $definition, $id) {
224 View Code Duplication
    if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) {
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...
225
      throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id));
226
    }
227
228
    $arguments = array();
229
    if (isset($definition['arguments'])) {
230
      $arguments = $definition['arguments'];
231
232
      if ($arguments instanceof \stdClass) {
233
        $arguments = $this->resolveServicesAndParameters($arguments);
234
      }
235
    }
236
237 View Code Duplication
    if (isset($definition['file'])) {
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...
238
      $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file'])));
239
      require_once $file;
240
    }
241
242 View Code Duplication
    if (isset($definition['factory'])) {
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...
243
      $factory = $definition['factory'];
244
      if (is_array($factory)) {
245
        $factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1]));
246
      }
247
      elseif (!is_string($factory)) {
248
        throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
249
      }
250
251
      $service = call_user_func_array($factory, $arguments);
252
    }
253
    else {
254
      $class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters(array($definition['class'])));
255
      $length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments);
256
257
      // Optimize class instantiation for services with up to 10 parameters as
258
      // ReflectionClass is noticeably slow.
259
      switch ($length) {
260
        case 0:
261
          $service = new $class();
262
          break;
263
264
        case 1:
265
          $service = new $class($arguments[0]);
266
          break;
267
268
        case 2:
269
          $service = new $class($arguments[0], $arguments[1]);
270
          break;
271
272
        case 3:
273
          $service = new $class($arguments[0], $arguments[1], $arguments[2]);
274
          break;
275
276
        case 4:
277
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
278
          break;
279
280
        case 5:
281
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
282
          break;
283
284
        case 6:
285
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
286
          break;
287
288
        case 7:
289
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
290
          break;
291
292
        case 8:
293
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
294
          break;
295
296
        case 9:
297
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
298
          break;
299
300
        case 10:
301
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
302
          break;
303
304
        default:
305
          $r = new \ReflectionClass($class);
306
          $service = $r->newInstanceArgs($arguments);
307
          break;
308
      }
309
    }
310
311
    // Share the service if it is public.
312 View Code Duplication
    if (!isset($definition['public']) || $definition['public'] !== FALSE) {
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...
313
      // Forward compatibility fix for Symfony 2.8 update.
314
      if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
315
        $this->services[$id] = $service;
316
      }
317
    }
318
319 View Code Duplication
    if (isset($definition['calls'])) {
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...
320
      foreach ($definition['calls'] as $call) {
321
        $method = $call[0];
322
        $arguments = array();
323
        if (!empty($call[1])) {
324
          $arguments = $call[1];
325
          if ($arguments instanceof \stdClass) {
326
            $arguments = $this->resolveServicesAndParameters($arguments);
327
          }
328
        }
329
        call_user_func_array(array($service, $method), $arguments);
330
      }
331
    }
332
333 View Code Duplication
    if (isset($definition['properties'])) {
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...
334
      if ($definition['properties'] instanceof \stdClass) {
335
        $definition['properties'] = $this->resolveServicesAndParameters($definition['properties']);
336
      }
337
      foreach ($definition['properties'] as $key => $value) {
338
        $service->{$key} = $value;
339
      }
340
    }
341
342 View Code Duplication
    if (isset($definition['configurator'])) {
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...
343
      $callable = $definition['configurator'];
344
      if (is_array($callable)) {
345
        $callable = $this->resolveServicesAndParameters($callable);
346
      }
347
348
      if (!is_callable($callable)) {
349
        throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service)));
350
      }
351
352
      call_user_func($callable, $service);
353
    }
354
355
    return $service;
356
  }
357
358
  /**
359
   * {@inheritdoc}
360
   */
361
  public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) {
362
    $this->services[$id] = $service;
363
  }
364
365
  /**
366
   * {@inheritdoc}
367
   */
368
  public function has($id) {
369
    return isset($this->aliases[$id]) || isset($this->services[$id]) || isset($this->serviceDefinitions[$id]) || array_key_exists($id, $this->services);
370
  }
371
372
  /**
373
   * {@inheritdoc}
374
   */
375
  public function getParameter($name) {
376
    if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) {
377
      if (!$name) {
378
        throw new ParameterNotFoundException($name);
379
      }
380
381
      throw new ParameterNotFoundException($name, NULL, NULL, NULL, $this->getParameterAlternatives($name));
382
    }
383
384
    return $this->parameters[$name];
385
  }
386
387
  /**
388
   * {@inheritdoc}
389
   */
390
  public function hasParameter($name) {
391
    return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters);
392
  }
393
394
  /**
395
   * {@inheritdoc}
396
   */
397
  public function setParameter($name, $value) {
398
    if ($this->frozen) {
399
      throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
400
    }
401
402
    $this->parameters[$name] = $value;
403
  }
404
405
  /**
406
   * {@inheritdoc}
407
   */
408
  public function initialized($id) {
409
    if (isset($this->aliases[$id])) {
410
      $id = $this->aliases[$id];
411
    }
412
413
    return isset($this->services[$id]) || array_key_exists($id, $this->services);
414
  }
415
416
  /**
417
   * Resolves arguments that represent services or variables to the real values.
418
   *
419
   * @param array|\stdClass $arguments
420
   *   The arguments to resolve.
421
   *
422
   * @return array
423
   *   The resolved arguments.
424
   *
425
   * @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException
426
   *   If a parameter/service could not be resolved.
427
   * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
428
   *   If an unknown type is met while resolving parameters and services.
429
   */
430
  protected function resolveServicesAndParameters($arguments) {
431
    // Check if this collection needs to be resolved.
432
    if ($arguments instanceof \stdClass) {
433
      if ($arguments->type !== 'collection') {
434
        throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $arguments->type));
435
      }
436
      // In case there is nothing to resolve, we are done here.
437
      if (!$arguments->resolve) {
438
        return $arguments->value;
439
      }
440
      $arguments = $arguments->value;
441
    }
442
443
    // Process the arguments.
444
    foreach ($arguments as $key => $argument) {
445
      // For this machine-optimized format, only \stdClass arguments are
446
      // processed and resolved. All other values are kept as is.
447
      if ($argument instanceof \stdClass) {
448
        $type = $argument->type;
449
450
        // Check for parameter.
451
        if ($type == 'parameter') {
452
          $name = $argument->name;
453
          if (!isset($this->parameters[$name])) {
454
            $arguments[$key] = $this->getParameter($name);
455
            // This can never be reached as getParameter() throws an Exception,
456
            // because we already checked that the parameter is not set above.
457
          }
458
459
          // Update argument.
460
          $argument = $arguments[$key] = $this->parameters[$name];
461
462
          // In case there is not a machine readable value (e.g. a service)
463
          // behind this resolved parameter, continue.
464
          if (!($argument instanceof \stdClass)) {
465
            continue;
466
          }
467
468
          // Fall through.
469
          $type = $argument->type;
470
        }
471
472
        // Create a service.
473
        if ($type == 'service') {
474
          $id = $argument->id;
475
476
          // Does the service already exist?
477
          if (isset($this->aliases[$id])) {
478
            $id = $this->aliases[$id];
479
          }
480
481
          if (isset($this->services[$id])) {
482
            $arguments[$key] = $this->services[$id];
483
            continue;
484
          }
485
486
          // Return the service.
487
          $arguments[$key] = $this->get($id, $argument->invalidBehavior);
488
489
          continue;
490
        }
491
        // Create private service.
492
        elseif ($type == 'private_service') {
493
          $id = $argument->id;
494
495
          // Does the private service already exist.
496
          if (isset($this->privateServices[$id])) {
497
            $arguments[$key] = $this->privateServices[$id];
498
            continue;
499
          }
500
501
          // Create the private service.
502
          $arguments[$key] = $this->createService($argument->value, $id);
503
          if ($argument->shared) {
504
            $this->privateServices[$id] = $arguments[$key];
505
          }
506
507
          continue;
508
        }
509
        // Check for collection.
510
        elseif ($type == 'collection') {
511
          $value = $argument->value;
512
513
          // Does this collection need resolving?
514
          if ($argument->resolve) {
515
            $arguments[$key] = $this->resolveServicesAndParameters($value);
516
          }
517
          else {
518
            $arguments[$key] = $value;
519
          }
520
521
          continue;
522
        }
523
524
        if ($type !== NULL) {
525
          throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $type));
526
        }
527
      }
528
    }
529
530
    return $arguments;
531
  }
532
533
  /**
534
   * Provides alternatives for a given array and key.
535
   *
536
   * @param string $search_key
537
   *   The search key to get alternatives for.
538
   * @param array $keys
539
   *   The search space to search for alternatives in.
540
   *
541
   * @return string[]
542
   *   An array of strings with suitable alternatives.
543
   */
544
  protected function getAlternatives($search_key, array $keys) {
545
    $alternatives = array();
546
    foreach ($keys as $key) {
547
      $lev = levenshtein($search_key, $key);
548
      if ($lev <= strlen($search_key) / 3 || strpos($key, $search_key) !== FALSE) {
549
        $alternatives[] = $key;
550
      }
551
    }
552
553
    return $alternatives;
554
  }
555
556
  /**
557
   * Provides alternatives in case a service was not found.
558
   *
559
   * @param string $id
560
   *   The service to get alternatives for.
561
   *
562
   * @return string[]
563
   *   An array of strings with suitable alternatives.
564
   */
565
  protected function getServiceAlternatives($id) {
566
    $all_service_keys = array_unique(array_merge(array_keys($this->services), array_keys($this->serviceDefinitions)));
567
    return $this->getAlternatives($id, $all_service_keys);
568
  }
569
570
  /**
571
   * Provides alternatives in case a parameter was not found.
572
   *
573
   * @param string $name
574
   *   The parameter to get alternatives for.
575
   *
576
   * @return string[]
577
   *   An array of strings with suitable alternatives.
578
   */
579
  protected function getParameterAlternatives($name) {
580
    return $this->getAlternatives($name, array_keys($this->parameters));
581
  }
582
583
584
  /**
585
   * {@inheritdoc}
586
   */
587
  public function enterScope($name) {
588
    throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
589
  }
590
591
  /**
592
   * {@inheritdoc}
593
   */
594
  public function leaveScope($name) {
595
    throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
596
  }
597
598
  /**
599
   * {@inheritdoc}
600
   */
601
  public function addScope(ScopeInterface $scope) {
602
    throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
603
  }
604
605
  /**
606
   * {@inheritdoc}
607
   */
608
  public function hasScope($name) {
609
    throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
610
  }
611
612
  /**
613
   * {@inheritdoc}
614
   */
615
  public function isScopeActive($name) {
616
    throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
617
  }
618
619
  /**
620
   * Gets all defined service IDs.
621
   *
622
   * @return array
623
   *   An array of all defined service IDs.
624
   */
625
  public function getServiceIds() {
626
    return array_keys($this->serviceDefinitions + $this->services);
627
  }
628
629
}
630