Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / lib / Drupal / Component / DependencyInjection / Dumper / OptimizedPhpArrayDumper.php
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->isCompiled();
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_compiled = $this->container->isCompiled();
107     return $this->prepareParameters($parameters, $is_compiled);
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         $code[$key] = $this->dumpValue($value);
313         if (is_object($code[$key])) {
314           $resolve = TRUE;
315         }
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 (is_string($value) && preg_match('/^\%(.*)\%$/', $value, $matches)) {
407       return $this->getParameterCall($matches[1]);
408     }
409     elseif ($value instanceof Expression) {
410       throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
411     }
412     elseif (is_object($value)) {
413       // Drupal specific: Instantiated objects have a _serviceId parameter.
414       if (isset($value->_serviceId)) {
415         return $this->getReferenceCall($value->_serviceId);
416       }
417       throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.');
418     }
419     elseif (is_resource($value)) {
420       throw new RuntimeException('Unable to dump a service container if a parameter is a resource.');
421     }
422
423     return $value;
424   }
425
426   /**
427    * Gets a service reference for a reference in a suitable PHP array format.
428    *
429    * The main difference is that this function treats references to private
430    * services differently and returns a private service reference instead of
431    * a normal reference.
432    *
433    * @param string $id
434    *   The ID of the service to get a reference for.
435    * @param \Symfony\Component\DependencyInjection\Reference|null $reference
436    *   (optional) The reference object to process; needed to get the invalid
437    *   behavior value.
438    *
439    * @return string|\stdClass
440    *   A suitable representation of the service reference.
441    */
442   protected function getReferenceCall($id, Reference $reference = NULL) {
443     $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
444
445     if ($reference !== NULL) {
446       $invalid_behavior = $reference->getInvalidBehavior();
447     }
448
449     // Private shared service.
450     if (isset($this->aliases[$id])) {
451       $id = $this->aliases[$id];
452     }
453     $definition = $this->container->getDefinition($id);
454     if (!$definition->isPublic()) {
455       // The ContainerBuilder does not share a private service, but this means a
456       // new service is instantiated every time. Use a private shared service to
457       // circumvent the problem.
458       return $this->getPrivateServiceCall($id, $definition, TRUE);
459     }
460
461     return $this->getServiceCall($id, $invalid_behavior);
462   }
463
464   /**
465    * Gets a service reference for an ID in a suitable PHP array format.
466    *
467    * @param string $id
468    *   The ID of the service to get a reference for.
469    * @param int $invalid_behavior
470    *   (optional) The invalid behavior of the service.
471    *
472    * @return string|\stdClass
473    *   A suitable representation of the service reference.
474    */
475   protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
476     return (object) [
477       'type' => 'service',
478       'id' => $id,
479       'invalidBehavior' => $invalid_behavior,
480     ];
481   }
482
483   /**
484    * Gets a parameter reference in a suitable PHP array format.
485    *
486    * @param string $name
487    *   The name of the parameter to get a reference for.
488    *
489    * @return string|\stdClass
490    *   A suitable representation of the parameter reference.
491    */
492   protected function getParameterCall($name) {
493     return (object) [
494       'type' => 'parameter',
495       'name' => $name,
496     ];
497   }
498
499   /**
500    * Whether this supports the machine-optimized format or not.
501    *
502    * @return bool
503    *   TRUE if this supports machine-optimized format, FALSE otherwise.
504    */
505   protected function supportsMachineFormat() {
506     return TRUE;
507   }
508
509 }