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