annotate core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php @ 11:bfffd8d7479a

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