Upgraded drupal core with security updates
[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->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     if (($scope = $definition->getScope()) !== ContainerInterface::SCOPE_CONTAINER) {
241       if ($scope === ContainerInterface::SCOPE_PROTOTYPE) {
242         // Scope prototype has been replaced with 'shared' => FALSE.
243         // This is a Symfony 2.8 forward compatibility fix.
244         // Reference: https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.8.md#dependencyinjection
245         $service['shared'] = FALSE;
246       }
247       else {
248         throw new InvalidArgumentException("The 'scope' definition is deprecated in Symfony 3.0 and not supported by Drupal 8.");
249       }
250     }
251
252     // By default services are shared, so just provide the flag, when needed.
253     if ($definition->isShared() === FALSE) {
254       $service['shared'] = $definition->isShared();
255     }
256
257     if (($decorated = $definition->getDecoratedService()) !== NULL) {
258       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.");
259     }
260
261     if ($callable = $definition->getFactory()) {
262       $service['factory'] = $this->dumpCallable($callable);
263     }
264
265     if ($callable = $definition->getConfigurator()) {
266       $service['configurator'] = $this->dumpCallable($callable);
267     }
268
269     return $service;
270   }
271
272   /**
273    * Dumps method calls to a PHP array.
274    *
275    * @param array $calls
276    *   An array of method calls.
277    *
278    * @return array
279    *   The PHP array representation of the method calls.
280    */
281   protected function dumpMethodCalls(array $calls) {
282     $code = [];
283
284     foreach ($calls as $key => $call) {
285       $method = $call[0];
286       $arguments = [];
287       if (!empty($call[1])) {
288         $arguments = $this->dumpCollection($call[1]);
289       }
290
291       $code[$key] = [$method, $arguments];
292     }
293
294     return $code;
295   }
296
297
298   /**
299    * Dumps a collection to a PHP array.
300    *
301    * @param mixed $collection
302    *   A collection to process.
303    * @param bool &$resolve
304    *   Used for passing the information to the caller whether the given
305    *   collection needed to be resolved or not. This is used for optimizing
306    *   deep arrays that don't need to be traversed.
307    *
308    * @return \stdClass|array
309    *   The collection in a suitable format.
310    */
311   protected function dumpCollection($collection, &$resolve = FALSE) {
312     $code = [];
313
314     foreach ($collection as $key => $value) {
315       if (is_array($value)) {
316         $resolve_collection = FALSE;
317         $code[$key] = $this->dumpCollection($value, $resolve_collection);
318
319         if ($resolve_collection) {
320           $resolve = TRUE;
321         }
322       }
323       else {
324         if (is_object($value)) {
325           $resolve = TRUE;
326         }
327         $code[$key] = $this->dumpValue($value);
328       }
329     }
330
331     if (!$resolve) {
332       return $collection;
333     }
334
335     return (object) [
336       'type' => 'collection',
337       'value' => $code,
338       'resolve' => $resolve,
339     ];
340   }
341
342   /**
343    * Dumps callable to a PHP array.
344    *
345    * @param array|callable $callable
346    *   The callable to process.
347    *
348    * @return callable
349    *   The processed callable.
350    */
351   protected function dumpCallable($callable) {
352     if (is_array($callable)) {
353       $callable[0] = $this->dumpValue($callable[0]);
354       $callable = [$callable[0], $callable[1]];
355     }
356
357     return $callable;
358   }
359
360   /**
361    * Gets a private service definition in a suitable format.
362    *
363    * @param string $id
364    *   The ID of the service to get a private definition for.
365    * @param \Symfony\Component\DependencyInjection\Definition $definition
366    *   The definition to process.
367    * @param bool $shared
368    *   (optional) Whether the service will be shared with others.
369    *   By default this parameter is FALSE.
370    *
371    * @return \stdClass
372    *   A very lightweight private service value object.
373    */
374   protected function getPrivateServiceCall($id, Definition $definition, $shared = FALSE) {
375     $service_definition = $this->getServiceDefinition($definition);
376     if (!$id) {
377       $hash = Crypt::hashBase64(serialize($service_definition));
378       $id = 'private__' . $hash;
379     }
380     return (object) [
381       'type' => 'private_service',
382       'id' => $id,
383       'value' => $service_definition,
384       'shared' => $shared,
385     ];
386   }
387
388   /**
389    * Dumps the value to PHP array format.
390    *
391    * @param mixed $value
392    *   The value to dump.
393    *
394    * @return mixed
395    *   The dumped value in a suitable format.
396    *
397    * @throws RuntimeException
398    *   When trying to dump object or resource.
399    */
400   protected function dumpValue($value) {
401     if (is_array($value)) {
402       $code = [];
403       foreach ($value as $k => $v) {
404         $code[$k] = $this->dumpValue($v);
405       }
406
407       return $code;
408     }
409     elseif ($value instanceof Reference) {
410       return $this->getReferenceCall((string) $value, $value);
411     }
412     elseif ($value instanceof Definition) {
413       return $this->getPrivateServiceCall(NULL, $value);
414     }
415     elseif ($value instanceof Parameter) {
416       return $this->getParameterCall((string) $value);
417     }
418     elseif ($value instanceof Expression) {
419       throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
420     }
421     elseif (is_object($value)) {
422       // Drupal specific: Instantiated objects have a _serviceId parameter.
423       if (isset($value->_serviceId)) {
424         return $this->getReferenceCall($value->_serviceId);
425       }
426       throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.');
427     }
428     elseif (is_resource($value)) {
429       throw new RuntimeException('Unable to dump a service container if a parameter is a resource.');
430     }
431
432     return $value;
433   }
434
435   /**
436    * Gets a service reference for a reference in a suitable PHP array format.
437    *
438    * The main difference is that this function treats references to private
439    * services differently and returns a private service reference instead of
440    * a normal reference.
441    *
442    * @param string $id
443    *   The ID of the service to get a reference for.
444    * @param \Symfony\Component\DependencyInjection\Reference|null $reference
445    *   (optional) The reference object to process; needed to get the invalid
446    *   behavior value.
447    *
448    * @return string|\stdClass
449    *   A suitable representation of the service reference.
450    */
451   protected function getReferenceCall($id, Reference $reference = NULL) {
452     $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
453
454     if ($reference !== NULL) {
455       $invalid_behavior = $reference->getInvalidBehavior();
456     }
457
458     // Private shared service.
459     if (isset($this->aliases[$id])) {
460       $id = $this->aliases[$id];
461     }
462     $definition = $this->container->getDefinition($id);
463     if (!$definition->isPublic()) {
464       // The ContainerBuilder does not share a private service, but this means a
465       // new service is instantiated every time. Use a private shared service to
466       // circumvent the problem.
467       return $this->getPrivateServiceCall($id, $definition, TRUE);
468     }
469
470     return $this->getServiceCall($id, $invalid_behavior);
471   }
472
473   /**
474    * Gets a service reference for an ID in a suitable PHP array format.
475    *
476    * @param string $id
477    *   The ID of the service to get a reference for.
478    * @param int $invalid_behavior
479    *   (optional) The invalid behavior of the service.
480    *
481    * @return string|\stdClass
482    *   A suitable representation of the service reference.
483    */
484   protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
485     return (object) [
486       'type' => 'service',
487       'id' => $id,
488       'invalidBehavior' => $invalid_behavior,
489     ];
490   }
491
492   /**
493    * Gets a parameter reference in a suitable PHP array format.
494    *
495    * @param string $name
496    *   The name of the parameter to get a reference for.
497    *
498    * @return string|\stdClass
499    *   A suitable representation of the parameter reference.
500    */
501   protected function getParameterCall($name) {
502     return (object) [
503       'type' => 'parameter',
504       'name' => $name,
505     ];
506   }
507
508   /**
509    * Whether this supports the machine-optimized format or not.
510    *
511    * @return bool
512    *   TRUE if this supports machine-optimized format, FALSE otherwise.
513    */
514   protected function supportsMachineFormat() {
515     return TRUE;
516   }
517
518 }