PhpArrayContainer   C
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 248
Duplicated Lines 50.4 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 58
c 2
b 1
f 0
lcom 1
cbo 3
dl 125
loc 248
rs 6.3005

3 Methods

Rating   Name   Duplication   Size   Complexity  
F createService() 110 132 34
B __construct() 15 15 7
D resolveServicesAndParameters() 0 86 17

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PhpArrayContainer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PhpArrayContainer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @file
4
 * Contains \Drupal\Component\DependencyInjection\PhpArrayContainer.
5
 */
6
7
namespace Drupal\Component\DependencyInjection;
8
9
use Symfony\Component\DependencyInjection\ContainerInterface;
10
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
11
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
12
13
/**
14
 * Provides a container optimized for Drupal's needs.
15
 *
16
 * This container implementation is compatible with the default Symfony
17
 * dependency injection container and similar to the Symfony ContainerBuilder
18
 * class, but optimized for speed.
19
 *
20
 * It is based on a human-readable PHP array container definition with a
21
 * structure very similar to the YAML container definition.
22
 *
23
 * @see \Drupal\Component\DependencyInjection\Container
24
 * @see \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper
25
 * @see \Drupal\Component\DependencyInjection\DependencySerializationTrait
26
 *
27
 * @ingroup container
28
 */
29
class PhpArrayContainer extends Container {
30
31
  /**
32
   * {@inheritdoc}
33
   */
34 View Code Duplication
  public function __construct(array $container_definition = array()) {
35
    if (isset($container_definition['machine_format']) && $container_definition['machine_format'] === TRUE) {
36
      throw new InvalidArgumentException('The machine-optimized format is not supported by this class. Use a human-readable format instead, e.g. as produced by \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.');
37
    }
38
39
    // Do not call the parent's constructor as it would bail on the
40
    // machine-optimized format.
41
    $this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array();
42
    $this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : array();
43
    $this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : array();
44
    $this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE;
45
46
    // Register the service_container with itself.
47
    $this->services['service_container'] = $this;
48
  }
49
50
  /**
51
   * {@inheritdoc}
52
   */
53
  protected function createService(array $definition, $id) {
54
    // This method is a verbatim copy of
55
    // \Drupal\Component\DependencyInjection\Container::createService
56
    // except for the following difference:
57
    // - There are no instanceof checks on \stdClass, which are used in the
58
    //   parent class to avoid resolving services and parameters when it is
59
    //   known from dumping that there is nothing to resolve.
60 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...
61
      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));
62
    }
63
64
    $arguments = array();
65
    if (isset($definition['arguments'])) {
66
      $arguments = $this->resolveServicesAndParameters($definition['arguments']);
67
    }
68
69 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...
70
      $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file'])));
71
      require_once $file;
72
    }
73
74 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...
75
      $factory = $definition['factory'];
76
      if (is_array($factory)) {
77
        $factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1]));
78
      }
79
      elseif (!is_string($factory)) {
80
        throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
81
      }
82
83
      $service = call_user_func_array($factory, $arguments);
84
    }
85
    else {
86
      $class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters(array($definition['class'])));
87
      $length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments);
88
89
      // Optimize class instantiation for services with up to 10 parameters as
90
      // reflection is noticeably slow.
91
      switch ($length) {
92
        case 0:
93
          $service = new $class();
94
          break;
95
96
        case 1:
97
          $service = new $class($arguments[0]);
98
          break;
99
100
        case 2:
101
          $service = new $class($arguments[0], $arguments[1]);
102
          break;
103
104
        case 3:
105
          $service = new $class($arguments[0], $arguments[1], $arguments[2]);
106
          break;
107
108
        case 4:
109
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
110
          break;
111
112
        case 5:
113
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
114
          break;
115
116
        case 6:
117
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
118
          break;
119
120
        case 7:
121
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
122
          break;
123
124
        case 8:
125
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
126
          break;
127
128
        case 9:
129
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
130
          break;
131
132
        case 10:
133
          $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
134
          break;
135
136
        default:
137
          $r = new \ReflectionClass($class);
138
          $service = $r->newInstanceArgs($arguments);
139
          break;
140
      }
141
    }
142
143
    // Share the service if it is public.
144 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...
145
      // Forward compatibility fix for Symfony 2.8 update.
146
      if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
147
        $this->services[$id] = $service;
148
      }
149
    }
150
151 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...
152
      foreach ($definition['calls'] as $call) {
153
        $method = $call[0];
154
        $arguments = array();
155
        if (!empty($call[1])) {
156
          $arguments = $call[1];
157
          $arguments = $this->resolveServicesAndParameters($arguments);
158
        }
159
        call_user_func_array(array($service, $method), $arguments);
160
      }
161
    }
162
163 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...
164
      $definition['properties'] = $this->resolveServicesAndParameters($definition['properties']);
165
      foreach ($definition['properties'] as $key => $value) {
166
        $service->{$key} = $value;
167
      }
168
    }
169
170 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...
171
      $callable = $definition['configurator'];
172
      if (is_array($callable)) {
173
        $callable = $this->resolveServicesAndParameters($callable);
174
      }
175
176
      if (!is_callable($callable)) {
177
        throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service)));
178
      }
179
180
      call_user_func($callable, $service);
181
    }
182
183
    return $service;
184
  }
185
186
  /**
187
   * {@inheritdoc}
188
   */
189
  protected function resolveServicesAndParameters($arguments) {
190
    // This method is different from the parent method only for the following
191
    // cases:
192
    // - A service is denoted by '@service' and not by a \stdClass object.
193
    // - A parameter is denoted by '%parameter%' and not by a \stdClass object.
194
    // - The depth of the tree representing the arguments is not known in
195
    //   advance, so it needs to be fully traversed recursively.
196
    foreach ($arguments as $key => $argument) {
197
      if ($argument instanceof \stdClass) {
198
        $type = $argument->type;
199
200
        // Private services are a special flavor: In case a private service is
201
        // only used by one other service, the ContainerBuilder uses a
202
        // Definition object as an argument, which does not have an ID set.
203
        // Therefore the format uses a \stdClass object to store the definition
204
        // and to be able to create the service on the fly.
205
        //
206
        // Note: When constructing a private service by hand, 'id' must be set.
207
        //
208
        // The PhpArrayDumper just uses the hash of the private service
209
        // definition to generate a unique ID.
210
        //
211
        // @see \Drupal\Component\DependecyInjection\Dumper\OptimizedPhpArrayDumper::getPrivateServiceCall
212
        if ($type == 'private_service') {
213
          $id = $argument->id;
214
215
          // Check if the private service already exists - in case it is shared.
216
          if (!empty($argument->shared) && isset($this->privateServices[$id])) {
217
            $arguments[$key] = $this->privateServices[$id];
218
            continue;
219
          }
220
221
          // Create a private service from a service definition.
222
          $arguments[$key] = $this->createService($argument->value, $id);
223
          if (!empty($argument->shared)) {
224
            $this->privateServices[$id] = $arguments[$key];
225
          }
226
227
          continue;
228
        }
229
230
        if ($type !== NULL) {
231
          throw new InvalidArgumentException("Undefined type '$type' while resolving parameters and services.");
232
        }
233
      }
234
235
      if (is_array($argument)) {
236
        $arguments[$key] = $this->resolveServicesAndParameters($argument);
237
        continue;
238
      }
239
240
      if (!is_string($argument)) {
241
        continue;
242
      }
243
244
      // Resolve parameters.
245
      if (isset($argument[0]) && $argument[0] === '%') {
246
        $name = substr($argument, 1, -1);
247
        if (!isset($this->parameters[$name])) {
248
          $arguments[$key] = $this->getParameter($name);
249
          // This can never be reached as getParameter() throws an Exception,
250
          // because we already checked that the parameter is not set above.
251
        }
252
        $argument = $this->parameters[$name];
253
        $arguments[$key] = $argument;
254
      }
255
256
      // Resolve services.
257
      if (isset($argument[0]) && $argument[0] === '@') {
258
        $id = substr($argument, 1);
259
        $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
260
        if ($id[0] === '?') {
261
          $id = substr($id, 1);
262
          $invalid_behavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
263
        }
264
        if (isset($this->services[$id])) {
265
          $arguments[$key] = $this->services[$id];
266
        }
267
        else {
268
          $arguments[$key] = $this->get($id, $invalid_behavior);
269
        }
270
      }
271
    }
272
273
    return $arguments;
274
  }
275
276
}
277