Completed
Push — 7.x-1.x ( 64022a...8b8e48 )
by Fabian
05:21
created

src/DependencyInjection/Container.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * @file
4
 * Contains \Drupal\service_container\DependencyInjection\Container
5
 */
6
7
namespace Drupal\service_container\DependencyInjection;
8
9
use ReflectionClass;
10
use RuntimeException;
11
use Symfony\Component\DependencyInjection\ScopeInterface;
12
13
/**
14
 * Container is a DI container that provides services to users of the class.
15
 *
16
 * @ingroup dic
17
 */
18
class Container implements ContainerInterface {
19
20
  /**
21
   * The parameters of the container.
22
   *
23
   * @var array
24
   */
25
  protected $parameters = array();
26
27
  /**
28
   * The service definitions of the container.
29
   *
30
   * @var array
31
   */
32
  protected $serviceDefinitions = array();
33
34
  /**
35
   * The instantiated services.
36
   *
37
   * @var array
38
   */
39
  protected $services = array();
40
41
  /**
42
   * The currently loading services.
43
   *
44
   * @var array
45
   */
46
  protected $loading = array();
47
48
  /**
49
   * Can the container parameters still be changed.
50
   *
51
   * For testing purposes the container needs to be changed.
52
   *
53
   * @var bool
54
   */
55
  protected $frozen = TRUE;
56
57
  /**
58
   * Constructs a new Container instance.
59
   *
60
   * @param array $container_definition
61
   *   An array containing the 'services' and 'parameters'
62
   * @param bool $frozen
63
   *   (optional) Determines whether the container parameters can be changed,
64
   *   defaults to TRUE;
65
   */
66
  public function __construct(array $container_definition, $frozen = TRUE) {
67
    $this->parameters = $container_definition['parameters'];
68
    $this->serviceDefinitions = $container_definition['services'];
69
    $this->services['service_container'] = $this;
70
    $this->frozen = $frozen;
71
  }
72
73
  /**
74
   * {@inheritdoc}
75
   */
76
  public function get($name, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
77 View Code Duplication
    if (isset($this->services[$name]) || ($invalidBehavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($name, $this->services))) {
0 ignored issues
show
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...
78
      return $this->services[$name];
79
    }
80
81
    if (isset($this->loading[$name])) {
82
      throw new RuntimeException(sprintf('Circular reference detected for service "%s", path: "%s".', $name, implode(' -> ', array_keys($this->loading))));
83
    }
84
85
    $definition = isset($this->serviceDefinitions[$name]) ? $this->serviceDefinitions[$name] : NULL;
86
87
    if (!$definition && $invalidBehavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
88
      throw new RuntimeException(sprintf('The "%s" service definition does not exist.', $name));
89
    }
90
91
    if (!$definition) {
92
      $this->services[$name] = NULL;
93
      return $this->services[$name];
94
    }
95
96
    if (isset($definition['alias'])) {
97
      return $this->get($definition['alias'], $invalidBehavior);
98
    }
99
100
    $this->loading[$name] = TRUE;
101
102
    $definition += array(
103
      'class' => '',
104
      'factory' => '',
105
      'factory_class' => '',
106
      'factory_method' => '',
107
      'factory_service' => '',
108
      'arguments' => array(),
109
      'properties' => array(),
110
      'calls' => array(),
111
      'tags' => array(),
112
    ); // @codeCoverageIgnore
113
114
    try {
115
      if (!empty($definition['arguments'])) {
116
        $arguments = $this->expandArguments($definition['arguments'], $invalidBehavior);
117
      } else {
118
        $arguments = array();
119
      }
120
      if (!empty($definition['factory'])) {
121
        $factory = $definition['factory'];
122
        if (is_array($factory)) {
123
          $factory = $this->expandArguments($factory, $invalidBehavior);
124
        }
125
        $service = call_user_func_array($factory, $arguments);
126
      }
127
      elseif (!empty($definition['factory_method'])) {
128
        $method = $definition['factory_method'];
129
130
        if (!empty($definition['factory_class'])) {
131
          $factory = $definition['factory_class'];
132
        }
133
        elseif (!empty($definition['factory_service'])) {
134
          $factory = $this->get($definition['factory_service'], $invalidBehavior);
135
        }
136
        else {
137
          throw new RuntimeException(sprintf('Cannot create service "%s" from factory method without a factory service or factory class.', $name));
138
        }
139
        $service = call_user_func_array(array($factory, $method), $arguments);
140
      }
141
      else {
142
        // @todo Allow dynamic class definitions via parameters.
143
        $class = $definition['class'];
144
        $length = count($arguments);
145
146
        switch ($length) {
147
          case 0:
148
            $service = new $class();
149
            break;
150
          case 1:
151
            $service = new $class($arguments[0]);
152
            break;
153
          case 2:
154
            $service = new $class($arguments[0], $arguments[1]);
155
            break;
156
          case 3:
157
            $service = new $class($arguments[0], $arguments[1], $arguments[2]);
158
            break;
159
          // @codeCoverageIgnoreStart
160
          case 4:
161
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
162
            break;
163
          case 5:
164
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
165
            break;
166
          case 6:
167
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
168
            break;
169
          case 7:
170
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
171
            break;
172
          case 8:
173
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
174
            break;
175
          case 9:
176
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
177
            break;
178
          case 10:
179
            $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
180
            break;
181
          default:
182
            $r = new ReflectionClass($class);
183
            $service = $r->newInstanceArgs($arguments);
184
            break;
185
        }
186
        // @codeCoverageIgnoreEnd
187
      }
188
    }
189
    catch (\Exception $e) {
190
      unset($this->loading[$name]);
191
      throw $e;
192
    }
193
    $this->services[$name] = $service;
194
    unset($this->loading[$name]);
195
196
    foreach ($definition['calls'] as $call) {
197
      $method = $call[0];
198
      $arguments = array();
199
      if (!empty($call[1])) {
200
        $arguments = $this->expandArguments($call[1], $invalidBehavior);
201
      }
202
      call_user_func_array(array($service, $method), $arguments);
203
    }
204
    foreach ($definition['properties'] as $key => $value) {
205
      $service->{$key} = $value;
206
    }
207
208
    return $this->services[$name];
209
  }
210
211
  /**
212
   * {@inheritdoc}
213
   */
214
  public function set($id, $service, $scope = self::SCOPE_CONTAINER) {
215
    if (isset($service)) {
216
      $service->_serviceId = $id;
217
    }
218
    $this->services[$id] = $service;
219
  }
220
221
  /**
222
   * {@inheritdoc}
223
   */
224
  public function has($id) {
225
    return isset($this->services[$id]) || isset($this->serviceDefinitions[$id]);
226
  }
227
228
  /**
229
   * {@inheritdoc}
230
   */
231
  public function createInstance($plugin_id, $service_definition) {
232
    $temporary_name = 'plugin_' . $plugin_id;
233
    $this->serviceDefinitions[$temporary_name] = $service_definition;
234
235
    $plugin = $this->get($temporary_name);
236
    unset($this->serviceDefinitions[$temporary_name]);
237
    unset($this->services[$temporary_name]);
238
239
    return $plugin;
240
  }
241
242
  /**
243
   * {@inheritdoc}
244
   */
245
  public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
246
    $definition = isset($this->serviceDefinitions[$plugin_id]) ? $this->serviceDefinitions[$plugin_id] : NULL;
247
248
    if (!$definition && $exception_on_invalid) {
249
      throw new RuntimeException(sprintf('The "%s" service definition does not exist.', $plugin_id));
250
    }
251
252
    return $definition;
253
  }
254
255
  /**
256
   * {@inheritdoc}
257
   */
258
  public function getDefinitions() {
259
    return $this->serviceDefinitions;
260
  }
261
262
  /**
263
   * {@inheritdoc}
264
   */
265
  public function hasDefinition($plugin_id) {
266
    return isset($this->serviceDefinitions[$plugin_id]);
267
  }
268
269
  /**
270
   * {@inheritdoc}
271
   */
272
  public function getParameter($name) {
273
    return isset($this->parameters[$name]) ? $this->parameters[$name] : NULL;
274
  }
275
276
  /**
277
   * {@inheritdoc}
278
   */
279
  public function hasParameter($name) {
280
    return isset($this->parameters[$name]);
281
  }
282
283
  /**
284
   * {@inheritdoc}
285
   */
286
  public function setParameter($name, $value) {
287
    if ($this->frozen) {
288
      throw new \BadMethodCallException("Container parameters can't be changed on runtime.");
289
    }
290
    $this->parameters[$name] = $value;
291
  }
292
293
  /**
294
   * Expands arguments from %parameter and @service to the resolved values.
295
   *
296
   * @param array $arguments
297
   *   The arguments to expand.
298
   * @param int $invalidBehavior
299
   *   The behavior when the service does not exist
300
   *
301
   * @return array
302
   *   The expanded arguments.
303
   *
304
   * @throws \RuntimeException if a parameter/service could not be resolved.
305
   */
306
  protected function expandArguments($arguments, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
307
    foreach ($arguments as $key => $argument) {
308
      if ($argument instanceof \stdClass) {
309
        $name = $argument->id;
310
        $this->serviceDefinitions[$name] = $argument->value;
311
        $arguments[$key] = $this->get($name, $invalidBehavior);
312
        unset($this->serviceDefinitions[$name]);
313
        unset($this->services[$name]);
314
        continue;
315
      }
316
317
      if (is_array($argument)) {
318
        $arguments[$key] = $this->expandArguments($argument, $invalidBehavior);
319
        continue;
320
      }
321
322
      if (!is_string($argument)) {
323
        continue;
324
      }
325
326
      if (strpos($argument, '%') === 0) {
327
        $name = substr($argument, 1, -1);
328 View Code Duplication
        if (!isset($this->parameters[$name])) {
0 ignored issues
show
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...
329
          if ($invalidBehavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
330
            throw new RuntimeException("Could not find parameter: $name");
331
          }
332
          $arguments[$key] = NULL;
333
          continue;
334
        }
335
        $arguments[$key] = $this->parameters[$name];
336
      }
337
      else if (strpos($argument, '@') === 0) {
338
        $name = substr($argument, 1);
339
        if (strpos($name, '?') === 0) {
340
          $name = substr($name, 1);
341
          $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
342
        }
343 View Code Duplication
        if (!isset($this->serviceDefinitions[$name])) {
0 ignored issues
show
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...
344
          if ($invalidBehavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
345
            throw new RuntimeException("Could not find service: $name");
346
          }
347
          $arguments[$key] = NULL;
348
          continue;
349
        }
350
        $arguments[$key] = $this->get($name, $invalidBehavior);
351
      }
352
    }
353
354
    return $arguments;
355
  }
356
357
  /**
358
   * {@inheritdoc}
359
   *
360
   * @codeCoverageIgnore
361
   */
362
  public function enterScope($name) {
363
    throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__));
364
  }
365
366
  /**
367
   * {@inheritdoc}
368
   *
369
   * @codeCoverageIgnore
370
   */
371
  public function leaveScope($name) {
372
    throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__));
373
  }
374
375
  /**
376
   * {@inheritdoc}
377
   *
378
   * @codeCoverageIgnore
379
   */
380
  public function addScope(ScopeInterface $scope) {
381
    throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__));
382
  }
383
384
  /**
385
   * {@inheritdoc}
386
   *
387
   * @codeCoverageIgnore
388
   */
389
  public function hasScope($name) {
390
    throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__));
391
  }
392
393
  /**
394
   * {@inheritdoc}
395
   *
396
   * @codeCoverageIgnore
397
   */
398
  public function isScopeActive($name) {
399
    throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__));
400
  }
401
402
  /**
403
   * {@inheritdoc}
404
   */
405
  public function initialized($id) {
406
    return isset($this->services[$id]);
407
  }
408
409
  /**
410
   * Camelizes a string.
411
   *
412
   * @param $name
413
   *   The string to camelize.
414
   *
415
   * @return string
416
   *   The camelized string.
417
   *
418
   */
419
  public static function camelize($name) {
420
    return strtr(ucwords(strtr($name, array('_' => ' ', '\\' => '_ '))), array(' ' => ''));
421
  }
422
423
  /**
424
   * Un-camelizes a string.
425
   *
426
   * @param $name
427
   *   The string to underscore.
428
   *
429
   * @return string
430
   *   The underscored string.
431
   *
432
   */
433
  public static function underscore($name) {
434
    return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name));
435
  }
436
}
437