diff --git a/core/lib/Drupal/Core/DependencyInjection/BootstrapContainer.php b/core/lib/Drupal/Core/DependencyInjection/BootstrapContainer.php index 99a42cb..1e9ff04 100644 --- a/core/lib/Drupal/Core/DependencyInjection/BootstrapContainer.php +++ b/core/lib/Drupal/Core/DependencyInjection/BootstrapContainer.php @@ -12,11 +12,11 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; /** - * Provides an interface compatible alternative to the default Symfony - * dependency injection container specially optimized for Drupal's needs. + * Provides an interface-compatible alternative to the default Symfony + * dependency injection container optimized for Drupal's needs. * * This container is based on a human-readable PHP array container definition - * with a structure very similar to the YAML file format. + * with a structure very similar to the YAML container definition. * * @see \Drupal\Core\DependencyInjection\Container * @see \Drupal\Core\DependencyInjection\Dumper\DebugPhpArrayDumper @@ -31,10 +31,11 @@ class BootstrapContainer extends Container { */ public function __construct(array $container_definition = array(), $frozen = FALSE) { if (isset($container_definition['machine_format']) && $container_definition['machine_format'] === TRUE) { - throw new InvalidArgumentException('The machine format is not supported by this class. Use a human-readable format instead.'); + throw new InvalidArgumentException('The machine format is not supported by this class. Use a human-readable format instead, e.g. as produced by \Drupal\Core\DependencyInjection\Dumper\PhpArrayDumper.'); } - // Do not call the parents constructor as it would bail on the machine format. + // Do not call the parents constructor as it would bail on the machine + // format. $this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array(); $this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : array(); $this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : array(); @@ -48,8 +49,14 @@ public function __construct(array $container_definition = array(), $frozen = FAL * {@inheritdoc} */ protected function createService($definition, $id) { + // This method is a verbatim copy of + // \Drupal\Core\DependencyInjection\Container::createService + // except for the following difference: + // - There are no instanceof checks on \stdClass, which are used in the + // parent class to avoid resolving services and parameters when it is + // known from dumping that there is nothing to resolve. if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) { - throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); + throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id)); } $arguments = array(); @@ -90,6 +97,8 @@ protected function createService($definition, $id) { $length = count($arguments); + // Optimize class instantiation for services with up to 10 parameters as + // ReflectionClass is noticeable slow. switch ($length) { case 0: $service = new $class(); @@ -162,7 +171,7 @@ protected function createService($definition, $id) { } if (!is_callable($callable)) { - throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service))); + throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service))); } call_user_func($callable, $service); @@ -175,25 +184,40 @@ protected function createService($definition, $id) { * {@inheritdoc} */ protected function resolveServicesAndParameters($arguments) { - // Process the arguments. + // This method is different from the parent method only for the following cases: + // - A service is denoted by '@service' and not by a \stdClass object. + // - A parameter is denoted by '%parameter%' and not by a \stdClass object. + // - Arguments are traversed recursively, there is no information how deep + // to traverse. + foreach ($arguments as $key => $argument) { - // Optimized code path. if ($argument instanceof \stdClass) { $type = $argument->type; - // Create private service. + // Private services are a special flavor: In case a private service is + // only used by one other service, the ContainerBuilder uses a + // Definition object as argument, which does not have a id set. + // Therefore the format uses a \stdClass object to store the definition + // and to be able to create the service on the fly. + // + // Note: When constructing a private service by hand, 'id' must be set. + // The PhpArrayDumper just uses the hash of the private service + // definition to generate a unique id. + // + // @see \Drupal\Core\DependecyInjection\Dumper\OptimizedPhpArrayDumper::getPrivateServiceCall + // if ($type == 'private_service') { $id = $argument->id; - // Does the private service already exist. - if (isset($this->privateServices[$id])) { + // Check if the private service already exists - in case it is shared. + if (!empty($argument->shared) && isset($this->privateServices[$id])) { $arguments[$key] = $this->privateServices[$id]; continue; } - // Create the private service. + // Create a private service from a service definition. $arguments[$key] = $this->createService($argument->value, $id); - if ($argument->shared) { + if (!empty($argument->shared)) { $this->privateServices[$id] = $arguments[$key]; } diff --git a/core/lib/Drupal/Core/DependencyInjection/Container.php b/core/lib/Drupal/Core/DependencyInjection/Container.php index c82b632..350d6e1 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Container.php +++ b/core/lib/Drupal/Core/DependencyInjection/Container.php @@ -20,15 +20,15 @@ /** * Provides an interface compatible alternative to the default Symfony - * dependency injection container specially optimized for Drupal's needs. + * dependency injection container optimized for Drupal's needs. * - * This container is based on a PHP array container definition with a structure - * very similar to the YAML file format. + * This container is based on a PHP array container definition dumped as + * a performance-optimized machine-readable format. * * The best way to initialize this container is to use a * \Drupal\Core\DependencyInjection\ContainerBuilder, compile it and then * retrieve the definition via - * \Drupal\Core\DependencyInjection\Dumper/PhpArray::getArray(). + * \Drupal\Core\DependencyInjection\Dumper\OptimizedPhpArrayDumper::getArray(). * * The retrieved array can be cached safely and then passed to this container * via the constructor. @@ -46,7 +46,7 @@ * getAServiceWithAnIdByCamelCase(). * - Scopes are explicitly not allowed, because Symfony 3.0 has deprecated * them. - * - Synchronized services are explicitly not allowed, because Symfony 3.0 has + * - Synchronized services are explicitly not supported, because Symfony 3.0 has * deprecated them. * * Drupal specifically does not allow serializing the container and any service @@ -121,7 +121,7 @@ class Container implements IntrospectableContainerInterface { */ public function __construct(array $container_definition = array(), $frozen = FALSE) { if (!empty($container_definition) && (!isset($container_definition['machine_format']) || $container_definition['machine_format'] !== TRUE)) { - throw new InvalidArgumentException('The human readable format is not supported by this class. Use a machine-readable format instead.'); + throw new InvalidArgumentException('The non-optimized format is not supported by this class. Use an optimized machine-readable format instead, e.g. as produced by \Drupal\Core\DependencyInjection\Dumper\OptimizedPhpArrayDumper.'); } $this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array(); @@ -161,7 +161,8 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV } // In case something else than ContainerInterface::NULL_ON_INVALID_REFERENCE - // is used, the actual wanted behavior is to re-try getting the service at a later point. + // is used, the actual wanted behavior is to re-try getting the service at + // a later point. if (!$definition) { return; } @@ -198,9 +199,23 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV return $service; } + /** + * Creates a service from a service definition. + * + * @param array $definition + * The service definition to create a service from. + * @param string $id + * The service identifier, neccessary so it can be shared if its public. + * + * @return object + * The service described by the service definition. + * + * @throws Symfony\Component\DependencyInjection\Exception\RuntimeException\RuntimeException When the service is a synthetic service. + * @throws Symfony\Component\DependencyInjection\Exception\InvalidArgumentException When the configurator callable is not callable. + */ protected function createService($definition, $id) { if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) { - throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); + throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id)); } $arguments = array(); @@ -243,6 +258,8 @@ protected function createService($definition, $id) { $class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters(array($definition['class']))); $length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments); + // Optimize class instantiation for services with up to 10 parameters as + // ReflectionClass is noticeable slow. switch ($length) { case 0: $service = new $class(); @@ -319,7 +336,7 @@ protected function createService($definition, $id) { } if (!is_callable($callable)) { - throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service))); + throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service))); } call_user_func($callable, $service); @@ -386,9 +403,7 @@ public function initialized($id) { } /** - * Resolves arguments from %parameter and @service to the real values. - * - * For performance reasons also a \stdClass based approach is supported. + * Resolves arguments from \stdClass objects to the real values. * * @param array|\stdClass $arguments * The arguments to resolve. @@ -414,7 +429,8 @@ protected function resolveServicesAndParameters($arguments) { // Process the arguments. foreach ($arguments as $key => $argument) { - // Optimized code path. + // For this machine-optimized format, only \stdClass arguments are + // processed and resolved. All other values are kept as is. if ($argument instanceof \stdClass) { $type = $argument->type; @@ -526,35 +542,35 @@ protected function getAlternatives($id) { * {@inheritdoc} */ public function enterScope($name) { - throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__)); + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } /** * {@inheritdoc} */ public function leaveScope($name) { - throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__)); + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } /** * {@inheritdoc} */ public function addScope(ScopeInterface $scope) { - throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__)); + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } /** * {@inheritdoc} */ public function hasScope($name) { - throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__)); + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } /** * {@inheritdoc} */ public function isScopeActive($name) { - throw new \BadMethodCallException(sprintf("'%s' is not implemented", __FUNCTION__)); + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } /** diff --git a/core/lib/Drupal/Core/DependencyInjection/Dumper/DebugPhpArrayDumper.php b/core/lib/Drupal/Core/DependencyInjection/Dumper/DebugPhpArrayDumper.php index 0f6326c..9bef32c 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Dumper/DebugPhpArrayDumper.php +++ b/core/lib/Drupal/Core/DependencyInjection/Dumper/DebugPhpArrayDumper.php @@ -27,8 +27,7 @@ * It is human readable, for a machine optimized version based on this one see * \Drupal\Core\DependencyInjection\Dumper\PhpArrayDumper. */ -class DebugPhpArrayDumper extends PhpArrayDumper -{ +class DebugPhpArrayDumper extends PhpArrayDumper { /** * {@inheritdoc} @@ -53,17 +52,17 @@ protected function dumpCollection($collection, &$resolve = FALSE) { */ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { - return sprintf('@?%s', $id); + return '@?' . $id; } - return sprintf('@%s', $id); + return '@' . $id; } /** * {@inheritdoc} */ protected function getParameterCall($name) { - return sprintf('%%%s%%', $name); + return '%' . $name . '%'; } /** diff --git a/core/lib/Drupal/Core/DependencyInjection/Dumper/PhpArrayDumper.php b/core/lib/Drupal/Core/DependencyInjection/Dumper/PhpArrayDumper.php index 7421fc5..9b2a637 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Dumper/PhpArrayDumper.php +++ b/core/lib/Drupal/Core/DependencyInjection/Dumper/PhpArrayDumper.php @@ -28,8 +28,7 @@ * It is machine readable, for a human readable version based on this one see * \Drupal\Core\DependencyInjection\Dumper\DebugPhpArrayDumper. */ -class PhpArrayDumper extends Dumper -{ +class PhpArrayDumper extends Dumper { /** * {@inheritdoc} */ @@ -53,7 +52,7 @@ public function getArray() { } /** - * Returns the aliases as a PHP Array. + * Returns the aliases as a PHP array. * * @return array * The aliases. diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index c00adc1..977639a 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -792,8 +792,11 @@ protected function initializeContainer() { try { $cache = $this->bootstrapContainer->get('cache.container')->get($this->getCacheKey()); } catch (\Exception $e) { - // Ignore exceptions thrown by the database. In case it is really not - // available, it will fail during container compilation. + // In the InstallerKernel the database is not available at this point. + // Not being able to load from cache is not a fatal condition, hence + // the error is ignored here. + // If the database is really not available, then it will fatal during + // service construction. $cache = FALSE; } if ($cache) { @@ -1229,6 +1232,9 @@ protected function cacheDrupalContainer($container_definition) { $this->bootstrapContainer->get('cache.container')->set($this->getCacheKey(), $container_definition); } catch (\Exception $e) { + // There is no way to get from the Cache API if the cache set was + // successful or not, hence an Exception is caught and the caller + // informed about the error condition. $saved = FALSE; }