annotate core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
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@14 68 $definition['frozen'] = $this->container->isCompiled();
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@14 106 $is_compiled = $this->container->isCompiled();
Chris@14 107 return $this->prepareParameters($parameters, $is_compiled);
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 * Dumps a collection to a PHP array.
Chris@0 287 *
Chris@0 288 * @param mixed $collection
Chris@0 289 * A collection to process.
Chris@0 290 * @param bool &$resolve
Chris@0 291 * Used for passing the information to the caller whether the given
Chris@0 292 * collection needed to be resolved or not. This is used for optimizing
Chris@0 293 * deep arrays that don't need to be traversed.
Chris@0 294 *
Chris@0 295 * @return \stdClass|array
Chris@0 296 * The collection in a suitable format.
Chris@0 297 */
Chris@0 298 protected function dumpCollection($collection, &$resolve = FALSE) {
Chris@0 299 $code = [];
Chris@0 300
Chris@0 301 foreach ($collection as $key => $value) {
Chris@0 302 if (is_array($value)) {
Chris@0 303 $resolve_collection = FALSE;
Chris@0 304 $code[$key] = $this->dumpCollection($value, $resolve_collection);
Chris@0 305
Chris@0 306 if ($resolve_collection) {
Chris@0 307 $resolve = TRUE;
Chris@0 308 }
Chris@0 309 }
Chris@0 310 else {
Chris@14 311 $code[$key] = $this->dumpValue($value);
Chris@14 312 if (is_object($code[$key])) {
Chris@0 313 $resolve = TRUE;
Chris@0 314 }
Chris@0 315 }
Chris@0 316 }
Chris@0 317
Chris@0 318 if (!$resolve) {
Chris@0 319 return $collection;
Chris@0 320 }
Chris@0 321
Chris@0 322 return (object) [
Chris@0 323 'type' => 'collection',
Chris@0 324 'value' => $code,
Chris@0 325 'resolve' => $resolve,
Chris@0 326 ];
Chris@0 327 }
Chris@0 328
Chris@0 329 /**
Chris@0 330 * Dumps callable to a PHP array.
Chris@0 331 *
Chris@0 332 * @param array|callable $callable
Chris@0 333 * The callable to process.
Chris@0 334 *
Chris@0 335 * @return callable
Chris@0 336 * The processed callable.
Chris@0 337 */
Chris@0 338 protected function dumpCallable($callable) {
Chris@0 339 if (is_array($callable)) {
Chris@0 340 $callable[0] = $this->dumpValue($callable[0]);
Chris@0 341 $callable = [$callable[0], $callable[1]];
Chris@0 342 }
Chris@0 343
Chris@0 344 return $callable;
Chris@0 345 }
Chris@0 346
Chris@0 347 /**
Chris@0 348 * Gets a private service definition in a suitable format.
Chris@0 349 *
Chris@0 350 * @param string $id
Chris@0 351 * The ID of the service to get a private definition for.
Chris@0 352 * @param \Symfony\Component\DependencyInjection\Definition $definition
Chris@0 353 * The definition to process.
Chris@0 354 * @param bool $shared
Chris@0 355 * (optional) Whether the service will be shared with others.
Chris@0 356 * By default this parameter is FALSE.
Chris@0 357 *
Chris@0 358 * @return \stdClass
Chris@0 359 * A very lightweight private service value object.
Chris@0 360 */
Chris@0 361 protected function getPrivateServiceCall($id, Definition $definition, $shared = FALSE) {
Chris@0 362 $service_definition = $this->getServiceDefinition($definition);
Chris@0 363 if (!$id) {
Chris@0 364 $hash = Crypt::hashBase64(serialize($service_definition));
Chris@0 365 $id = 'private__' . $hash;
Chris@0 366 }
Chris@0 367 return (object) [
Chris@0 368 'type' => 'private_service',
Chris@0 369 'id' => $id,
Chris@0 370 'value' => $service_definition,
Chris@0 371 'shared' => $shared,
Chris@0 372 ];
Chris@0 373 }
Chris@0 374
Chris@0 375 /**
Chris@0 376 * Dumps the value to PHP array format.
Chris@0 377 *
Chris@0 378 * @param mixed $value
Chris@0 379 * The value to dump.
Chris@0 380 *
Chris@0 381 * @return mixed
Chris@0 382 * The dumped value in a suitable format.
Chris@0 383 *
Chris@0 384 * @throws RuntimeException
Chris@0 385 * When trying to dump object or resource.
Chris@0 386 */
Chris@0 387 protected function dumpValue($value) {
Chris@0 388 if (is_array($value)) {
Chris@0 389 $code = [];
Chris@0 390 foreach ($value as $k => $v) {
Chris@0 391 $code[$k] = $this->dumpValue($v);
Chris@0 392 }
Chris@0 393
Chris@0 394 return $code;
Chris@0 395 }
Chris@0 396 elseif ($value instanceof Reference) {
Chris@0 397 return $this->getReferenceCall((string) $value, $value);
Chris@0 398 }
Chris@0 399 elseif ($value instanceof Definition) {
Chris@0 400 return $this->getPrivateServiceCall(NULL, $value);
Chris@0 401 }
Chris@0 402 elseif ($value instanceof Parameter) {
Chris@0 403 return $this->getParameterCall((string) $value);
Chris@0 404 }
Chris@14 405 elseif (is_string($value) && preg_match('/^\%(.*)\%$/', $value, $matches)) {
Chris@14 406 return $this->getParameterCall($matches[1]);
Chris@14 407 }
Chris@0 408 elseif ($value instanceof Expression) {
Chris@0 409 throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
Chris@0 410 }
Chris@0 411 elseif (is_object($value)) {
Chris@0 412 // Drupal specific: Instantiated objects have a _serviceId parameter.
Chris@0 413 if (isset($value->_serviceId)) {
Chris@0 414 return $this->getReferenceCall($value->_serviceId);
Chris@0 415 }
Chris@0 416 throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.');
Chris@0 417 }
Chris@0 418 elseif (is_resource($value)) {
Chris@0 419 throw new RuntimeException('Unable to dump a service container if a parameter is a resource.');
Chris@0 420 }
Chris@0 421
Chris@0 422 return $value;
Chris@0 423 }
Chris@0 424
Chris@0 425 /**
Chris@0 426 * Gets a service reference for a reference in a suitable PHP array format.
Chris@0 427 *
Chris@0 428 * The main difference is that this function treats references to private
Chris@0 429 * services differently and returns a private service reference instead of
Chris@0 430 * a normal reference.
Chris@0 431 *
Chris@0 432 * @param string $id
Chris@0 433 * The ID of the service to get a reference for.
Chris@0 434 * @param \Symfony\Component\DependencyInjection\Reference|null $reference
Chris@0 435 * (optional) The reference object to process; needed to get the invalid
Chris@0 436 * behavior value.
Chris@0 437 *
Chris@0 438 * @return string|\stdClass
Chris@0 439 * A suitable representation of the service reference.
Chris@0 440 */
Chris@0 441 protected function getReferenceCall($id, Reference $reference = NULL) {
Chris@0 442 $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
Chris@0 443
Chris@0 444 if ($reference !== NULL) {
Chris@0 445 $invalid_behavior = $reference->getInvalidBehavior();
Chris@0 446 }
Chris@0 447
Chris@0 448 // Private shared service.
Chris@0 449 if (isset($this->aliases[$id])) {
Chris@0 450 $id = $this->aliases[$id];
Chris@0 451 }
Chris@0 452 $definition = $this->container->getDefinition($id);
Chris@0 453 if (!$definition->isPublic()) {
Chris@0 454 // The ContainerBuilder does not share a private service, but this means a
Chris@0 455 // new service is instantiated every time. Use a private shared service to
Chris@0 456 // circumvent the problem.
Chris@0 457 return $this->getPrivateServiceCall($id, $definition, TRUE);
Chris@0 458 }
Chris@0 459
Chris@0 460 return $this->getServiceCall($id, $invalid_behavior);
Chris@0 461 }
Chris@0 462
Chris@0 463 /**
Chris@0 464 * Gets a service reference for an ID in a suitable PHP array format.
Chris@0 465 *
Chris@0 466 * @param string $id
Chris@0 467 * The ID of the service to get a reference for.
Chris@0 468 * @param int $invalid_behavior
Chris@0 469 * (optional) The invalid behavior of the service.
Chris@0 470 *
Chris@0 471 * @return string|\stdClass
Chris@0 472 * A suitable representation of the service reference.
Chris@0 473 */
Chris@0 474 protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
Chris@0 475 return (object) [
Chris@0 476 'type' => 'service',
Chris@0 477 'id' => $id,
Chris@0 478 'invalidBehavior' => $invalid_behavior,
Chris@0 479 ];
Chris@0 480 }
Chris@0 481
Chris@0 482 /**
Chris@0 483 * Gets a parameter reference in a suitable PHP array format.
Chris@0 484 *
Chris@0 485 * @param string $name
Chris@0 486 * The name of the parameter to get a reference for.
Chris@0 487 *
Chris@0 488 * @return string|\stdClass
Chris@0 489 * A suitable representation of the parameter reference.
Chris@0 490 */
Chris@0 491 protected function getParameterCall($name) {
Chris@0 492 return (object) [
Chris@0 493 'type' => 'parameter',
Chris@0 494 'name' => $name,
Chris@0 495 ];
Chris@0 496 }
Chris@0 497
Chris@0 498 /**
Chris@0 499 * Whether this supports the machine-optimized format or not.
Chris@0 500 *
Chris@0 501 * @return bool
Chris@0 502 * TRUE if this supports machine-optimized format, FALSE otherwise.
Chris@0 503 */
Chris@0 504 protected function supportsMachineFormat() {
Chris@0 505 return TRUE;
Chris@0 506 }
Chris@0 507
Chris@0 508 }