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