OptimizedPhpArrayDumper::getServiceDefinition()   F
last analyzed

Complexity

Conditions 14
Paths 2816

Size

Total Lines 65
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 65
rs 2.7252
cc 14
eloc 34
nc 2816
nop 1

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\Dumper\OptimizedPhpArrayDumper.
6
 */
7
8
namespace Drupal\Component\DependencyInjection\Dumper;
9
10
use Symfony\Component\DependencyInjection\ContainerInterface;
11
use Symfony\Component\DependencyInjection\Definition;
12
use Symfony\Component\DependencyInjection\Parameter;
13
use Symfony\Component\DependencyInjection\Reference;
14
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
15
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
16
use Symfony\Component\DependencyInjection\Dumper\Dumper;
17
use Symfony\Component\ExpressionLanguage\Expression;
18
19
/**
20
 * OptimizedPhpArrayDumper dumps a service container as a serialized PHP array.
21
 *
22
 * The format of this dumper is very similar to the internal structure of the
23
 * ContainerBuilder, but based on PHP arrays and \stdClass objects instead of
24
 * rich value objects for performance reasons.
25
 *
26
 * By removing the abstraction and optimizing some cases like deep collections,
27
 * fewer classes need to be loaded, fewer function calls need to be executed and
28
 * fewer run time checks need to be made.
29
 *
30
 * In addition to that, this container dumper treats private services as
31
 * strictly private with their own private services storage, whereas in the
32
 * Symfony service container builder and PHP dumper, shared private services can
33
 * still be retrieved via get() from the container.
34
 *
35
 * It is machine-optimized, for a human-readable version based on this one see
36
 * \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.
37
 *
38
 * @see \Drupal\Component\DependencyInjection\Container
39
 */
40
class OptimizedPhpArrayDumper extends Dumper {
41
42
  /**
43
   * Whether to serialize service definitions or not.
44
   *
45
   * Service definitions are serialized by default to avoid having to
46
   * unserialize the whole container on loading time, which improves early
47
   * bootstrap performance for e.g. the page cache.
48
   *
49
   * @var bool
50
   */
51
  protected $serialize = TRUE;
52
53
  /**
54
   * {@inheritdoc}
55
   */
56
  public function dump(array $options = array()) {
57
    return serialize($this->getArray());
58
  }
59
60
  /**
61
   * Gets the service container definition as a PHP array.
62
   *
63
   * @return array
64
   *   A PHP array representation of the service container.
65
   */
66
  public function getArray() {
67
    $definition = array();
68
    $definition['aliases'] = $this->getAliases();
69
    $definition['parameters'] = $this->getParameters();
70
    $definition['services'] = $this->getServiceDefinitions();
71
    $definition['frozen'] = $this->container->isFrozen();
72
    $definition['machine_format'] = $this->supportsMachineFormat();
73
    return $definition;
74
  }
75
76
  /**
77
   * Gets the aliases as a PHP array.
78
   *
79
   * @return array
80
   *   The aliases.
81
   */
82
  protected function getAliases() {
83
    $alias_definitions = array();
84
85
    $aliases = $this->container->getAliases();
86
    foreach ($aliases as $alias => $id) {
87
      $id = (string) $id;
88
      while (isset($aliases[$id])) {
89
        $id = (string) $aliases[$id];
90
      }
91
      $alias_definitions[$alias] = $id;
92
    }
93
94
    return $alias_definitions;
95
  }
96
97
  /**
98
   * Gets parameters of the container as a PHP array.
99
   *
100
   * @return array
101
   *   The escaped and prepared parameters of the container.
102
   */
103 View Code Duplication
  protected function getParameters() {
104
    if (!$this->container->getParameterBag()->all()) {
105
      return array();
106
    }
107
108
    $parameters = $this->container->getParameterBag()->all();
109
    $is_frozen = $this->container->isFrozen();
110
    return $this->prepareParameters($parameters, $is_frozen);
111
  }
112
113
  /**
114
   * Gets services of the container as a PHP array.
115
   *
116
   * @return array
117
   *   The service definitions.
118
   */
119
  protected function getServiceDefinitions() {
120
    if (!$this->container->getDefinitions()) {
121
      return array();
122
    }
123
124
    $services = array();
125
    foreach ($this->container->getDefinitions() as $id => $definition) {
126
      // Only store public service definitions, references to shared private
127
      // services are handled in ::getReferenceCall().
128
      if ($definition->isPublic()) {
129
        $service_definition = $this->getServiceDefinition($definition);
130
        $services[$id] = $this->serialize ? serialize($service_definition) : $service_definition;
131
      }
132
    }
133
134
    return $services;
135
  }
136
137
  /**
138
   * Prepares parameters for the PHP array dumping.
139
   *
140
   * @param array $parameters
141
   *   An array of parameters.
142
   * @param bool $escape
143
   *   Whether keys with '%' should be escaped or not.
144
   *
145
   * @return array
146
   *   An array of prepared parameters.
147
   */
148 View Code Duplication
  protected function prepareParameters(array $parameters, $escape = TRUE) {
149
    $filtered = array();
150
    foreach ($parameters as $key => $value) {
151
      if (is_array($value)) {
152
        $value = $this->prepareParameters($value, $escape);
153
      }
154
      elseif ($value instanceof Reference) {
155
        $value = $this->dumpValue($value);
156
      }
157
158
      $filtered[$key] = $value;
159
    }
160
161
    return $escape ? $this->escape($filtered) : $filtered;
162
  }
163
164
  /**
165
   * Escapes parameters.
166
   *
167
   * @param array $parameters
168
   *   The parameters to escape for '%' characters.
169
   *
170
   * @return array
171
   *   The escaped parameters.
172
   */
173 View Code Duplication
  protected function escape(array $parameters) {
174
    $args = array();
175
176
    foreach ($parameters as $key => $value) {
177
      if (is_array($value)) {
178
        $args[$key] = $this->escape($value);
179
      }
180
      elseif (is_string($value)) {
181
        $args[$key] = str_replace('%', '%%', $value);
182
      }
183
      else {
184
        $args[$key] = $value;
185
      }
186
    }
187
188
    return $args;
189
  }
190
191
  /**
192
   * Gets a service definition as PHP array.
193
   *
194
   * @param \Symfony\Component\DependencyInjection\Definition $definition
195
   *   The definition to process.
196
   *
197
   * @return array
198
   *   The service definition as PHP array.
199
   *
200
   * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
201
   *   Thrown when the definition is marked as decorated, or with an explicit
202
   *   scope different from SCOPE_CONTAINER and SCOPE_PROTOTYPE.
203
   */
204
  protected function getServiceDefinition(Definition $definition) {
205
    $service = array();
206
    if ($definition->getClass()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $definition->getClass() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
207
      $service['class'] = $definition->getClass();
208
    }
209
210
    if (!$definition->isPublic()) {
211
      $service['public'] = FALSE;
212
    }
213
214
    if ($definition->getFile()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $definition->getFile() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
215
      $service['file'] = $definition->getFile();
216
    }
217
218
    if ($definition->isSynthetic()) {
219
      $service['synthetic'] = TRUE;
220
    }
221
222
    if ($definition->isLazy()) {
223
      $service['lazy'] = TRUE;
224
    }
225
226
    if ($definition->getArguments()) {
227
      $arguments = $definition->getArguments();
228
      $service['arguments'] = $this->dumpCollection($arguments);
229
      $service['arguments_count'] = count($arguments);
230
    }
231
    else {
232
      $service['arguments_count'] = 0;
233
    }
234
235
    if ($definition->getProperties()) {
236
      $service['properties'] = $this->dumpCollection($definition->getProperties());
237
    }
238
239
    if ($definition->getMethodCalls()) {
240
      $service['calls'] = $this->dumpMethodCalls($definition->getMethodCalls());
241
    }
242
243
    if (($scope = $definition->getScope()) !== ContainerInterface::SCOPE_CONTAINER) {
244
      if ($scope === ContainerInterface::SCOPE_PROTOTYPE) {
245
        // Scope prototype has been replaced with 'shared' => FALSE.
246
        // This is a Symfony 2.8 forward compatibility fix.
247
        // Reference: https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.8.md#dependencyinjection
248
        $service['shared'] = FALSE;
249
      }
250
      else {
251
        throw new InvalidArgumentException("The 'scope' definition is deprecated in Symfony 3.0 and not supported by Drupal 8.");
252
      }
253
    }
254
255
    if (($decorated = $definition->getDecoratedService()) !== NULL) {
256
      throw new InvalidArgumentException("The 'decorated' definition is not supported by the Drupal 8 run-time container. The Container Builder should have resolved that during the DecoratorServicePass compiler pass.");
257
    }
258
259
    if ($callable = $definition->getFactory()) {
260
      $service['factory'] = $this->dumpCallable($callable);
261
    }
262
263
    if ($callable = $definition->getConfigurator()) {
264
      $service['configurator'] = $this->dumpCallable($callable);
265
    }
266
267
    return $service;
268
  }
269
270
  /**
271
   * Dumps method calls to a PHP array.
272
   *
273
   * @param array $calls
274
   *   An array of method calls.
275
   *
276
   * @return array
277
   *   The PHP array representation of the method calls.
278
   */
279
  protected function dumpMethodCalls(array $calls) {
280
    $code = array();
281
282
    foreach ($calls as $key => $call) {
283
      $method = $call[0];
284
      $arguments = array();
285
      if (!empty($call[1])) {
286
        $arguments = $this->dumpCollection($call[1]);
287
      }
288
289
      $code[$key] = array($method, $arguments);
290
    }
291
292
    return $code;
293
  }
294
295
296
  /**
297
   * Dumps a collection to a PHP array.
298
   *
299
   * @param mixed $collection
300
   *   A collection to process.
301
   * @param bool &$resolve
302
   *   Used for passing the information to the caller whether the given
303
   *   collection needed to be resolved or not. This is used for optimizing
304
   *   deep arrays that don't need to be traversed.
305
   *
306
   * @return \stdClass|array
307
   *   The collection in a suitable format.
308
   */
309
  protected function dumpCollection($collection, &$resolve = FALSE) {
310
    $code = array();
311
312
    foreach ($collection as $key => $value) {
313
      if (is_array($value)) {
314
        $resolve_collection = FALSE;
315
        $code[$key] = $this->dumpCollection($value, $resolve_collection);
316
317
        if ($resolve_collection) {
318
          $resolve = TRUE;
319
        }
320
      }
321
      else {
322
        if (is_object($value)) {
323
          $resolve = TRUE;
324
        }
325
        $code[$key] = $this->dumpValue($value);
326
      }
327
    }
328
329
    if (!$resolve) {
330
      return $collection;
331
    }
332
333
    return (object) array(
334
      'type' => 'collection',
335
      'value' => $code,
336
      'resolve' => $resolve,
337
    );
338
  }
339
340
  /**
341
   * Dumps callable to a PHP array.
342
   *
343
   * @param array|callable $callable
344
   *   The callable to process.
345
   *
346
   * @return callable
347
   *   The processed callable.
348
   */
349
  protected function dumpCallable($callable) {
350
    if (is_array($callable)) {
351
      $callable[0] = $this->dumpValue($callable[0]);
352
      $callable = array($callable[0], $callable[1]);
353
    }
354
355
    return $callable;
356
  }
357
358
  /**
359
   * Gets a private service definition in a suitable format.
360
   *
361
   * @param string $id
362
   *   The ID of the service to get a private definition for.
363
   * @param \Symfony\Component\DependencyInjection\Definition $definition
364
   *   The definition to process.
365
   * @param bool $shared
366
   *   (optional) Whether the service will be shared with others.
367
   *   By default this parameter is FALSE.
368
   *
369
   * @return \stdClass
370
   *   A very lightweight private service value object.
371
   */
372
  protected function getPrivateServiceCall($id, Definition $definition, $shared = FALSE) {
373
    $service_definition = $this->getServiceDefinition($definition);
374
    if (!$id) {
375
      $hash = hash('sha1', serialize($service_definition));
376
      $id = 'private__' . $hash;
377
    }
378
    return (object) array(
379
      'type' => 'private_service',
380
      'id' => $id,
381
      'value' => $service_definition,
382
      'shared' => $shared,
383
    );
384
  }
385
386
  /**
387
   * Dumps the value to PHP array format.
388
   *
389
   * @param mixed $value
390
   *   The value to dump.
391
   *
392
   * @return mixed
393
   *   The dumped value in a suitable format.
394
   *
395
   * @throws RuntimeException
396
   *   When trying to dump object or resource.
397
   */
398
  protected function dumpValue($value) {
399
    if (is_array($value)) {
400
      $code = array();
401
      foreach ($value as $k => $v) {
402
        $code[$k] = $this->dumpValue($v);
403
      }
404
405
      return $code;
406
    }
407
    elseif ($value instanceof Reference) {
408
      return $this->getReferenceCall((string) $value, $value);
409
    }
410
    elseif ($value instanceof Definition) {
411
      return $this->getPrivateServiceCall(NULL, $value);
412
    }
413
    elseif ($value instanceof Parameter) {
414
      return $this->getParameterCall((string) $value);
415
    }
416
    elseif ($value instanceof Expression) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\ExpressionLanguage\Expression does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
417
      throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
418
    }
419 View Code Duplication
    elseif (is_object($value)) {
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...
420
      // Drupal specific: Instantiated objects have a _serviceId parameter.
421
      if (isset($value->_serviceId)) {
422
        return $this->getReferenceCall($value->_serviceId);
423
      }
424
      throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.');
425
    }
426
    elseif (is_resource($value)) {
427
      throw new RuntimeException('Unable to dump a service container if a parameter is a resource.');
428
    }
429
430
    return $value;
431
  }
432
433
  /**
434
   * Gets a service reference for a reference in a suitable PHP array format.
435
   *
436
   * The main difference is that this function treats references to private
437
   * services differently and returns a private service reference instead of
438
   * a normal reference.
439
   *
440
   * @param string $id
441
   *   The ID of the service to get a reference for.
442
   * @param \Symfony\Component\DependencyInjection\Reference|NULL $reference
443
   *   (optional) The reference object to process; needed to get the invalid
444
   *   behavior value.
445
   *
446
   * @return string|\stdClass
447
   *   A suitable representation of the service reference.
448
   */
449
  protected function getReferenceCall($id, Reference $reference = NULL) {
450
    $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
451
452
    if ($reference !== NULL) {
453
      $invalid_behavior = $reference->getInvalidBehavior();
454
    }
455
456
    // Private shared service.
457
    $definition = $this->container->getDefinition($id);
458
    if (!$definition->isPublic()) {
459
      // The ContainerBuilder does not share a private service, but this means a
460
      // new service is instantiated every time. Use a private shared service to
461
      // circumvent the problem.
462
      return $this->getPrivateServiceCall($id, $definition, TRUE);
463
    }
464
465
    return $this->getServiceCall($id, $invalid_behavior);
466
  }
467
468
  /**
469
   * Gets a service reference for an ID in a suitable PHP array format.
470
   *
471
   * @param string $id
472
   *   The ID of the service to get a reference for.
473
   * @param int $invalid_behavior
474
   *   (optional) The invalid behavior of the service.
475
   *
476
   * @return string|\stdClass
477
   *   A suitable representation of the service reference.
478
   */
479
  protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
480
    return (object) array(
481
      'type' => 'service',
482
      'id' => $id,
483
      'invalidBehavior' => $invalid_behavior,
484
    );
485
  }
486
487
  /**
488
   * Gets a parameter reference in a suitable PHP array format.
489
   *
490
   * @param string $name
491
   *   The name of the parameter to get a reference for.
492
   *
493
   * @return string|\stdClass
494
   *   A suitable representation of the parameter reference.
495
   */
496
  protected function getParameterCall($name) {
497
    return (object) array(
498
      'type' => 'parameter',
499
      'name' => $name,
500
    );
501
  }
502
503
  /**
504
   * Whether this supports the machine-optimized format or not.
505
   *
506
   * @return bool
507
   *   TRUE if this supports machine-optimized format, FALSE otherwise.
508
   */
509
  protected function supportsMachineFormat() {
510
    return TRUE;
511
  }
512
513
}
514