Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Component\DependencyInjection\Dumper; | |
4 | |
5 use Drupal\Component\Utility\Crypt; | |
6 use Symfony\Component\DependencyInjection\ContainerInterface; | |
7 use Symfony\Component\DependencyInjection\Definition; | |
8 use Symfony\Component\DependencyInjection\Parameter; | |
9 use Symfony\Component\DependencyInjection\Reference; | |
10 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; | |
11 use Symfony\Component\DependencyInjection\Exception\RuntimeException; | |
12 use Symfony\Component\DependencyInjection\Dumper\Dumper; | |
13 use Symfony\Component\ExpressionLanguage\Expression; | |
14 | |
15 /** | |
16 * OptimizedPhpArrayDumper dumps a service container as a serialized PHP array. | |
17 * | |
18 * The format of this dumper is very similar to the internal structure of the | |
19 * ContainerBuilder, but based on PHP arrays and \stdClass objects instead of | |
20 * rich value objects for performance reasons. | |
21 * | |
22 * By removing the abstraction and optimizing some cases like deep collections, | |
23 * fewer classes need to be loaded, fewer function calls need to be executed and | |
24 * fewer run time checks need to be made. | |
25 * | |
26 * In addition to that, this container dumper treats private services as | |
27 * strictly private with their own private services storage, whereas in the | |
28 * Symfony service container builder and PHP dumper, shared private services can | |
29 * still be retrieved via get() from the container. | |
30 * | |
31 * It is machine-optimized, for a human-readable version based on this one see | |
32 * \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper. | |
33 * | |
34 * @see \Drupal\Component\DependencyInjection\Container | |
35 */ | |
36 class OptimizedPhpArrayDumper extends Dumper { | |
37 | |
38 /** | |
39 * Whether to serialize service definitions or not. | |
40 * | |
41 * Service definitions are serialized by default to avoid having to | |
42 * unserialize the whole container on loading time, which improves early | |
43 * bootstrap performance for e.g. the page cache. | |
44 * | |
45 * @var bool | |
46 */ | |
47 protected $serialize = TRUE; | |
48 | |
49 /** | |
50 * {@inheritdoc} | |
51 */ | |
52 public function dump(array $options = []) { | |
53 return serialize($this->getArray()); | |
54 } | |
55 | |
56 /** | |
57 * Gets the service container definition as a PHP array. | |
58 * | |
59 * @return array | |
60 * A PHP array representation of the service container. | |
61 */ | |
62 public function getArray() { | |
63 $definition = []; | |
64 $this->aliases = $this->getAliases(); | |
65 $definition['aliases'] = $this->getAliases(); | |
66 $definition['parameters'] = $this->getParameters(); | |
67 $definition['services'] = $this->getServiceDefinitions(); | |
68 $definition['frozen'] = $this->container->isFrozen(); | |
69 $definition['machine_format'] = $this->supportsMachineFormat(); | |
70 return $definition; | |
71 } | |
72 | |
73 /** | |
74 * Gets the aliases as a PHP array. | |
75 * | |
76 * @return array | |
77 * The aliases. | |
78 */ | |
79 protected function getAliases() { | |
80 $alias_definitions = []; | |
81 | |
82 $aliases = $this->container->getAliases(); | |
83 foreach ($aliases as $alias => $id) { | |
84 $id = (string) $id; | |
85 while (isset($aliases[$id])) { | |
86 $id = (string) $aliases[$id]; | |
87 } | |
88 $alias_definitions[$alias] = $id; | |
89 } | |
90 | |
91 return $alias_definitions; | |
92 } | |
93 | |
94 /** | |
95 * Gets parameters of the container as a PHP array. | |
96 * | |
97 * @return array | |
98 * The escaped and prepared parameters of the container. | |
99 */ | |
100 protected function getParameters() { | |
101 if (!$this->container->getParameterBag()->all()) { | |
102 return []; | |
103 } | |
104 | |
105 $parameters = $this->container->getParameterBag()->all(); | |
106 $is_frozen = $this->container->isFrozen(); | |
107 return $this->prepareParameters($parameters, $is_frozen); | |
108 } | |
109 | |
110 /** | |
111 * Gets services of the container as a PHP array. | |
112 * | |
113 * @return array | |
114 * The service definitions. | |
115 */ | |
116 protected function getServiceDefinitions() { | |
117 if (!$this->container->getDefinitions()) { | |
118 return []; | |
119 } | |
120 | |
121 $services = []; | |
122 foreach ($this->container->getDefinitions() as $id => $definition) { | |
123 // Only store public service definitions, references to shared private | |
124 // services are handled in ::getReferenceCall(). | |
125 if ($definition->isPublic()) { | |
126 $service_definition = $this->getServiceDefinition($definition); | |
127 $services[$id] = $this->serialize ? serialize($service_definition) : $service_definition; | |
128 } | |
129 } | |
130 | |
131 return $services; | |
132 } | |
133 | |
134 /** | |
135 * Prepares parameters for the PHP array dumping. | |
136 * | |
137 * @param array $parameters | |
138 * An array of parameters. | |
139 * @param bool $escape | |
140 * Whether keys with '%' should be escaped or not. | |
141 * | |
142 * @return array | |
143 * An array of prepared parameters. | |
144 */ | |
145 protected function prepareParameters(array $parameters, $escape = TRUE) { | |
146 $filtered = []; | |
147 foreach ($parameters as $key => $value) { | |
148 if (is_array($value)) { | |
149 $value = $this->prepareParameters($value, $escape); | |
150 } | |
151 elseif ($value instanceof Reference) { | |
152 $value = $this->dumpValue($value); | |
153 } | |
154 | |
155 $filtered[$key] = $value; | |
156 } | |
157 | |
158 return $escape ? $this->escape($filtered) : $filtered; | |
159 } | |
160 | |
161 /** | |
162 * Escapes parameters. | |
163 * | |
164 * @param array $parameters | |
165 * The parameters to escape for '%' characters. | |
166 * | |
167 * @return array | |
168 * The escaped parameters. | |
169 */ | |
170 protected function escape(array $parameters) { | |
171 $args = []; | |
172 | |
173 foreach ($parameters as $key => $value) { | |
174 if (is_array($value)) { | |
175 $args[$key] = $this->escape($value); | |
176 } | |
177 elseif (is_string($value)) { | |
178 $args[$key] = str_replace('%', '%%', $value); | |
179 } | |
180 else { | |
181 $args[$key] = $value; | |
182 } | |
183 } | |
184 | |
185 return $args; | |
186 } | |
187 | |
188 /** | |
189 * Gets a service definition as PHP array. | |
190 * | |
191 * @param \Symfony\Component\DependencyInjection\Definition $definition | |
192 * The definition to process. | |
193 * | |
194 * @return array | |
195 * The service definition as PHP array. | |
196 * | |
197 * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException | |
198 * Thrown when the definition is marked as decorated, or with an explicit | |
199 * scope different from SCOPE_CONTAINER and SCOPE_PROTOTYPE. | |
200 */ | |
201 protected function getServiceDefinition(Definition $definition) { | |
202 $service = []; | |
203 if ($definition->getClass()) { | |
204 $service['class'] = $definition->getClass(); | |
205 } | |
206 | |
207 if (!$definition->isPublic()) { | |
208 $service['public'] = FALSE; | |
209 } | |
210 | |
211 if ($definition->getFile()) { | |
212 $service['file'] = $definition->getFile(); | |
213 } | |
214 | |
215 if ($definition->isSynthetic()) { | |
216 $service['synthetic'] = TRUE; | |
217 } | |
218 | |
219 if ($definition->isLazy()) { | |
220 $service['lazy'] = TRUE; | |
221 } | |
222 | |
223 if ($definition->getArguments()) { | |
224 $arguments = $definition->getArguments(); | |
225 $service['arguments'] = $this->dumpCollection($arguments); | |
226 $service['arguments_count'] = count($arguments); | |
227 } | |
228 else { | |
229 $service['arguments_count'] = 0; | |
230 } | |
231 | |
232 if ($definition->getProperties()) { | |
233 $service['properties'] = $this->dumpCollection($definition->getProperties()); | |
234 } | |
235 | |
236 if ($definition->getMethodCalls()) { | |
237 $service['calls'] = $this->dumpMethodCalls($definition->getMethodCalls()); | |
238 } | |
239 | |
240 // By default services are shared, so just provide the flag, when needed. | |
241 if ($definition->isShared() === FALSE) { | |
242 $service['shared'] = $definition->isShared(); | |
243 } | |
244 | |
245 if (($decorated = $definition->getDecoratedService()) !== NULL) { | |
246 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."); | |
247 } | |
248 | |
249 if ($callable = $definition->getFactory()) { | |
250 $service['factory'] = $this->dumpCallable($callable); | |
251 } | |
252 | |
253 if ($callable = $definition->getConfigurator()) { | |
254 $service['configurator'] = $this->dumpCallable($callable); | |
255 } | |
256 | |
257 return $service; | |
258 } | |
259 | |
260 /** | |
261 * Dumps method calls to a PHP array. | |
262 * | |
263 * @param array $calls | |
264 * An array of method calls. | |
265 * | |
266 * @return array | |
267 * The PHP array representation of the method calls. | |
268 */ | |
269 protected function dumpMethodCalls(array $calls) { | |
270 $code = []; | |
271 | |
272 foreach ($calls as $key => $call) { | |
273 $method = $call[0]; | |
274 $arguments = []; | |
275 if (!empty($call[1])) { | |
276 $arguments = $this->dumpCollection($call[1]); | |
277 } | |
278 | |
279 $code[$key] = [$method, $arguments]; | |
280 } | |
281 | |
282 return $code; | |
283 } | |
284 | |
285 | |
286 /** | |
287 * Dumps a collection to a PHP array. | |
288 * | |
289 * @param mixed $collection | |
290 * A collection to process. | |
291 * @param bool &$resolve | |
292 * Used for passing the information to the caller whether the given | |
293 * collection needed to be resolved or not. This is used for optimizing | |
294 * deep arrays that don't need to be traversed. | |
295 * | |
296 * @return \stdClass|array | |
297 * The collection in a suitable format. | |
298 */ | |
299 protected function dumpCollection($collection, &$resolve = FALSE) { | |
300 $code = []; | |
301 | |
302 foreach ($collection as $key => $value) { | |
303 if (is_array($value)) { | |
304 $resolve_collection = FALSE; | |
305 $code[$key] = $this->dumpCollection($value, $resolve_collection); | |
306 | |
307 if ($resolve_collection) { | |
308 $resolve = TRUE; | |
309 } | |
310 } | |
311 else { | |
312 if (is_object($value)) { | |
313 $resolve = TRUE; | |
314 } | |
315 $code[$key] = $this->dumpValue($value); | |
316 } | |
317 } | |
318 | |
319 if (!$resolve) { | |
320 return $collection; | |
321 } | |
322 | |
323 return (object) [ | |
324 'type' => 'collection', | |
325 'value' => $code, | |
326 'resolve' => $resolve, | |
327 ]; | |
328 } | |
329 | |
330 /** | |
331 * Dumps callable to a PHP array. | |
332 * | |
333 * @param array|callable $callable | |
334 * The callable to process. | |
335 * | |
336 * @return callable | |
337 * The processed callable. | |
338 */ | |
339 protected function dumpCallable($callable) { | |
340 if (is_array($callable)) { | |
341 $callable[0] = $this->dumpValue($callable[0]); | |
342 $callable = [$callable[0], $callable[1]]; | |
343 } | |
344 | |
345 return $callable; | |
346 } | |
347 | |
348 /** | |
349 * Gets a private service definition in a suitable format. | |
350 * | |
351 * @param string $id | |
352 * The ID of the service to get a private definition for. | |
353 * @param \Symfony\Component\DependencyInjection\Definition $definition | |
354 * The definition to process. | |
355 * @param bool $shared | |
356 * (optional) Whether the service will be shared with others. | |
357 * By default this parameter is FALSE. | |
358 * | |
359 * @return \stdClass | |
360 * A very lightweight private service value object. | |
361 */ | |
362 protected function getPrivateServiceCall($id, Definition $definition, $shared = FALSE) { | |
363 $service_definition = $this->getServiceDefinition($definition); | |
364 if (!$id) { | |
365 $hash = Crypt::hashBase64(serialize($service_definition)); | |
366 $id = 'private__' . $hash; | |
367 } | |
368 return (object) [ | |
369 'type' => 'private_service', | |
370 'id' => $id, | |
371 'value' => $service_definition, | |
372 'shared' => $shared, | |
373 ]; | |
374 } | |
375 | |
376 /** | |
377 * Dumps the value to PHP array format. | |
378 * | |
379 * @param mixed $value | |
380 * The value to dump. | |
381 * | |
382 * @return mixed | |
383 * The dumped value in a suitable format. | |
384 * | |
385 * @throws RuntimeException | |
386 * When trying to dump object or resource. | |
387 */ | |
388 protected function dumpValue($value) { | |
389 if (is_array($value)) { | |
390 $code = []; | |
391 foreach ($value as $k => $v) { | |
392 $code[$k] = $this->dumpValue($v); | |
393 } | |
394 | |
395 return $code; | |
396 } | |
397 elseif ($value instanceof Reference) { | |
398 return $this->getReferenceCall((string) $value, $value); | |
399 } | |
400 elseif ($value instanceof Definition) { | |
401 return $this->getPrivateServiceCall(NULL, $value); | |
402 } | |
403 elseif ($value instanceof Parameter) { | |
404 return $this->getParameterCall((string) $value); | |
405 } | |
406 elseif ($value instanceof Expression) { | |
407 throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); | |
408 } | |
409 elseif (is_object($value)) { | |
410 // Drupal specific: Instantiated objects have a _serviceId parameter. | |
411 if (isset($value->_serviceId)) { | |
412 return $this->getReferenceCall($value->_serviceId); | |
413 } | |
414 throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.'); | |
415 } | |
416 elseif (is_resource($value)) { | |
417 throw new RuntimeException('Unable to dump a service container if a parameter is a resource.'); | |
418 } | |
419 | |
420 return $value; | |
421 } | |
422 | |
423 /** | |
424 * Gets a service reference for a reference in a suitable PHP array format. | |
425 * | |
426 * The main difference is that this function treats references to private | |
427 * services differently and returns a private service reference instead of | |
428 * a normal reference. | |
429 * | |
430 * @param string $id | |
431 * The ID of the service to get a reference for. | |
432 * @param \Symfony\Component\DependencyInjection\Reference|null $reference | |
433 * (optional) The reference object to process; needed to get the invalid | |
434 * behavior value. | |
435 * | |
436 * @return string|\stdClass | |
437 * A suitable representation of the service reference. | |
438 */ | |
439 protected function getReferenceCall($id, Reference $reference = NULL) { | |
440 $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; | |
441 | |
442 if ($reference !== NULL) { | |
443 $invalid_behavior = $reference->getInvalidBehavior(); | |
444 } | |
445 | |
446 // Private shared service. | |
447 if (isset($this->aliases[$id])) { | |
448 $id = $this->aliases[$id]; | |
449 } | |
450 $definition = $this->container->getDefinition($id); | |
451 if (!$definition->isPublic()) { | |
452 // The ContainerBuilder does not share a private service, but this means a | |
453 // new service is instantiated every time. Use a private shared service to | |
454 // circumvent the problem. | |
455 return $this->getPrivateServiceCall($id, $definition, TRUE); | |
456 } | |
457 | |
458 return $this->getServiceCall($id, $invalid_behavior); | |
459 } | |
460 | |
461 /** | |
462 * Gets a service reference for an ID in a suitable PHP array format. | |
463 * | |
464 * @param string $id | |
465 * The ID of the service to get a reference for. | |
466 * @param int $invalid_behavior | |
467 * (optional) The invalid behavior of the service. | |
468 * | |
469 * @return string|\stdClass | |
470 * A suitable representation of the service reference. | |
471 */ | |
472 protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { | |
473 return (object) [ | |
474 'type' => 'service', | |
475 'id' => $id, | |
476 'invalidBehavior' => $invalid_behavior, | |
477 ]; | |
478 } | |
479 | |
480 /** | |
481 * Gets a parameter reference in a suitable PHP array format. | |
482 * | |
483 * @param string $name | |
484 * The name of the parameter to get a reference for. | |
485 * | |
486 * @return string|\stdClass | |
487 * A suitable representation of the parameter reference. | |
488 */ | |
489 protected function getParameterCall($name) { | |
490 return (object) [ | |
491 'type' => 'parameter', | |
492 'name' => $name, | |
493 ]; | |
494 } | |
495 | |
496 /** | |
497 * Whether this supports the machine-optimized format or not. | |
498 * | |
499 * @return bool | |
500 * TRUE if this supports machine-optimized format, FALSE otherwise. | |
501 */ | |
502 protected function supportsMachineFormat() { | |
503 return TRUE; | |
504 } | |
505 | |
506 } |