summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2015-08-21 13:56:38 (GMT)
committerAlex Pott2015-08-21 13:56:38 (GMT)
commit22fbcd414eea90a8c07491c8ef8a033689460c38 (patch)
tree7d3901d53cb3dc8f4df4740dad504fea16d951f7
parent8cf5b80c28730271f294fc2e8db2013635d3b403 (diff)
Issue #2497243 by Fabianx, znerol, fgm, Wim Leers, darol100, jhedstrom, hussainweb, pfrenssen, neclimdul, jibran, Nitesh Sethia, dawehner, chx, catch, benjy, Aki Tendo: Replace Symfony container with a Drupal one, stored in cache
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/Container.php629
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php513
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/Dumper/PhpArrayDumper.php77
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php276
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/composer.json18
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Container.php9
-rw-r--r--core/lib/Drupal/Core/DrupalKernel.php188
-rw-r--r--core/lib/Drupal/Core/DrupalKernelInterface.php10
-rw-r--r--core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php32
-rw-r--r--core/modules/system/src/Tests/ServiceProvider/ServiceProviderTest.php12
-rw-r--r--core/modules/system/src/Tests/ServiceProvider/ServiceProviderWebTest.php41
-rw-r--r--core/modules/system/src/Tests/System/UncaughtExceptionTest.php18
-rw-r--r--core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php1188
-rw-r--r--core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php621
-rw-r--r--core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/PhpArrayDumperTest.php59
-rw-r--r--core/tests/Drupal/Tests/Component/DependencyInjection/Fixture/container_test_file_service_test_service_function.data16
-rw-r--r--core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php54
17 files changed, 3632 insertions, 129 deletions
diff --git a/core/lib/Drupal/Component/DependencyInjection/Container.php b/core/lib/Drupal/Component/DependencyInjection/Container.php
new file mode 100644
index 0000000..df25ae8
--- /dev/null
+++ b/core/lib/Drupal/Component/DependencyInjection/Container.php
@@ -0,0 +1,629 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\DependencyInjection\Container.
+ */
+
+namespace Drupal\Component\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
+use Symfony\Component\DependencyInjection\ScopeInterface;
+use Symfony\Component\DependencyInjection\Exception\LogicException;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
+use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
+
+/**
+ * Provides a container optimized for Drupal's needs.
+ *
+ * This container implementation is compatible with the default Symfony
+ * dependency injection container and similar to the Symfony ContainerBuilder
+ * class, but optimized for speed.
+ *
+ * It 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 Container Builder,
+ * compile it and then retrieve the definition via
+ * \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper::getArray().
+ *
+ * The retrieved array can be cached safely and then passed to this container
+ * via the constructor.
+ *
+ * As the container is unfrozen by default, a second parameter can be passed to
+ * the container to "freeze" the parameter bag.
+ *
+ * This container is different in behavior from the default Symfony container in
+ * the following ways:
+ *
+ * - It only allows lowercase service and parameter names, though it does only
+ * enforce it via assertions for performance reasons.
+ * - The following functions, that are not part of the interface, are explicitly
+ * not supported: getParameterBag(), isFrozen(), compile(),
+ * getAServiceWithAnIdByCamelCase().
+ * - The function getServiceIds() was added as it has a use-case in core and
+ * contrib.
+ * - Scopes are explicitly not allowed, because Symfony 2.8 has deprecated
+ * them and they will be removed in Symfony 3.0.
+ * - Synchronized services are explicitly not supported, because Symfony 2.8 has
+ * deprecated them and they will be removed in Symfony 3.0.
+ *
+ * @ingroup container
+ */
+class Container implements IntrospectableContainerInterface {
+
+ /**
+ * The parameters of the container.
+ *
+ * @var array
+ */
+ protected $parameters = array();
+
+ /**
+ * The aliases of the container.
+ *
+ * @var array
+ */
+ protected $aliases = array();
+
+ /**
+ * The service definitions of the container.
+ *
+ * @var array
+ */
+ protected $serviceDefinitions = array();
+
+ /**
+ * The instantiated services.
+ *
+ * @var array
+ */
+ protected $services = array();
+
+ /**
+ * The instantiated private services.
+ *
+ * @var array
+ */
+ protected $privateServices = array();
+
+ /**
+ * The currently loading services.
+ *
+ * @var array
+ */
+ protected $loading = array();
+
+ /**
+ * Whether the container parameters can still be changed.
+ *
+ * For testing purposes the container needs to be changed.
+ *
+ * @var bool
+ */
+ protected $frozen = TRUE;
+
+ /**
+ * Constructs a new Container instance.
+ *
+ * @param array $container_definition
+ * An array containing the following keys:
+ * - aliases: The aliases of the container.
+ * - parameters: The parameters of the container.
+ * - services: The service definitions of the container.
+ * - frozen: Whether the container definition came from a frozen
+ * container builder or not.
+ * - machine_format: Whether this container definition uses the optimized
+ * machine-readable container format.
+ */
+ public function __construct(array $container_definition = array()) {
+ if (!empty($container_definition) && (!isset($container_definition['machine_format']) || $container_definition['machine_format'] !== TRUE)) {
+ 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\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.');
+ }
+
+ $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();
+ $this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE;
+
+ // Register the service_container with itself.
+ $this->services['service_container'] = $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ if (isset($this->aliases[$id])) {
+ $id = $this->aliases[$id];
+ }
+
+ // Re-use shared service instance if it exists.
+ if (isset($this->services[$id]) || ($invalid_behavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($id, $this->services))) {
+ return $this->services[$id];
+ }
+
+ if (isset($this->loading[$id])) {
+ throw new ServiceCircularReferenceException($id, array_keys($this->loading));
+ }
+
+ $definition = isset($this->serviceDefinitions[$id]) ? $this->serviceDefinitions[$id] : NULL;
+
+ if (!$definition && $invalid_behavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ if (!$id) {
+ throw new ServiceNotFoundException($id);
+ }
+
+ throw new ServiceNotFoundException($id, NULL, NULL, $this->getServiceAlternatives($id));
+ }
+
+ // 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.
+ if (!$definition) {
+ return;
+ }
+
+ // Definition is a keyed array, so [0] is only defined when it is a
+ // serialized string.
+ if (isset($definition[0])) {
+ $definition = unserialize($definition);
+ }
+
+ // Now create the service.
+ $this->loading[$id] = TRUE;
+
+ try {
+ $service = $this->createService($definition, $id);
+ }
+ catch (\Exception $e) {
+ unset($this->loading[$id]);
+
+ // Remove a potentially shared service that was constructed incompletely.
+ if (array_key_exists($id, $this->services)) {
+ unset($this->services[$id]);
+ }
+
+ if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) {
+ return;
+ }
+
+ throw $e;
+ }
+
+ unset($this->loading[$id]);
+
+ 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, necessary so it can be shared if its public.
+ *
+ * @return object
+ * The service described by the service definition.
+ *
+ * @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ * Thrown when the service is a synthetic service.
+ * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ * Thrown when the configurator callable in $definition['configurator'] is
+ * not actually a callable.
+ * @throws \ReflectionException
+ * Thrown when the service class takes more than 10 parameters to construct,
+ * and cannot be instantiated.
+ */
+ protected function createService(array $definition, $id) {
+ if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) {
+ 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();
+ if (isset($definition['arguments'])) {
+ $arguments = $definition['arguments'];
+
+ if ($arguments instanceof \stdClass) {
+ $arguments = $this->resolveServicesAndParameters($arguments);
+ }
+ }
+
+ if (isset($definition['file'])) {
+ $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file'])));
+ require_once $file;
+ }
+
+ if (isset($definition['factory'])) {
+ $factory = $definition['factory'];
+ if (is_array($factory)) {
+ $factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1]));
+ }
+ elseif (!is_string($factory)) {
+ throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
+ }
+
+ $service = call_user_func_array($factory, $arguments);
+ }
+ else {
+ $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 noticeably slow.
+ switch ($length) {
+ case 0:
+ $service = new $class();
+ break;
+
+ case 1:
+ $service = new $class($arguments[0]);
+ break;
+
+ case 2:
+ $service = new $class($arguments[0], $arguments[1]);
+ break;
+
+ case 3:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2]);
+ break;
+
+ case 4:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
+ break;
+
+ case 5:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
+ break;
+
+ case 6:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
+ break;
+
+ case 7:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
+ break;
+
+ case 8:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
+ break;
+
+ case 9:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
+ break;
+
+ case 10:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
+ break;
+
+ default:
+ $r = new \ReflectionClass($class);
+ $service = $r->newInstanceArgs($arguments);
+ break;
+ }
+ }
+
+ // Share the service if it is public.
+ if (!isset($definition['public']) || $definition['public'] !== FALSE) {
+ // Forward compatibility fix for Symfony 2.8 update.
+ if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
+ $this->services[$id] = $service;
+ }
+ }
+
+ if (isset($definition['calls'])) {
+ foreach ($definition['calls'] as $call) {
+ $method = $call[0];
+ $arguments = array();
+ if (!empty($call[1])) {
+ $arguments = $call[1];
+ if ($arguments instanceof \stdClass) {
+ $arguments = $this->resolveServicesAndParameters($arguments);
+ }
+ }
+ call_user_func_array(array($service, $method), $arguments);
+ }
+ }
+
+ if (isset($definition['properties'])) {
+ if ($definition['properties'] instanceof \stdClass) {
+ $definition['properties'] = $this->resolveServicesAndParameters($definition['properties']);
+ }
+ foreach ($definition['properties'] as $key => $value) {
+ $service->{$key} = $value;
+ }
+ }
+
+ if (isset($definition['configurator'])) {
+ $callable = $definition['configurator'];
+ if (is_array($callable)) {
+ $callable = $this->resolveServicesAndParameters($callable);
+ }
+
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service)));
+ }
+
+ call_user_func($callable, $service);
+ }
+
+ return $service;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) {
+ $this->services[$id] = $service;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($id) {
+ return isset($this->services[$id]) || isset($this->serviceDefinitions[$id]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParameter($name) {
+ if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) {
+ if (!$name) {
+ throw new ParameterNotFoundException($name);
+ }
+
+ throw new ParameterNotFoundException($name, NULL, NULL, NULL, $this->getParameterAlternatives($name));
+ }
+
+ return $this->parameters[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasParameter($name) {
+ return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setParameter($name, $value) {
+ if ($this->frozen) {
+ throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
+ }
+
+ $this->parameters[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialized($id) {
+ if (isset($this->aliases[$id])) {
+ $id = $this->aliases[$id];
+ }
+
+ return isset($this->services[$id]) || array_key_exists($id, $this->services);
+ }
+
+ /**
+ * Resolves arguments that represent services or variables to the real values.
+ *
+ * @param array|\stdClass $arguments
+ * The arguments to resolve.
+ *
+ * @return array
+ * The resolved arguments.
+ *
+ * @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ * If a parameter/service could not be resolved.
+ * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ * If an unknown type is met while resolving parameters and services.
+ */
+ protected function resolveServicesAndParameters($arguments) {
+ // Check if this collection needs to be resolved.
+ if ($arguments instanceof \stdClass) {
+ if ($arguments->type !== 'collection') {
+ throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $arguments->type));
+ }
+ // In case there is nothing to resolve, we are done here.
+ if (!$arguments->resolve) {
+ return $arguments->value;
+ }
+ $arguments = $arguments->value;
+ }
+
+ // Process the arguments.
+ foreach ($arguments as $key => $argument) {
+ // 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;
+
+ // Check for parameter.
+ if ($type == 'parameter') {
+ $name = $argument->name;
+ if (!isset($this->parameters[$name])) {
+ $arguments[$key] = $this->getParameter($name);
+ // This can never be reached as getParameter() throws an Exception,
+ // because we already checked that the parameter is not set above.
+ }
+
+ // Update argument.
+ $argument = $arguments[$key] = $this->parameters[$name];
+
+ // In case there is not a machine readable value (e.g. a service)
+ // behind this resolved parameter, continue.
+ if (!($argument instanceof \stdClass)) {
+ continue;
+ }
+
+ // Fall through.
+ $type = $argument->type;
+ }
+
+ // Create a service.
+ if ($type == 'service') {
+ $id = $argument->id;
+
+ // Does the service already exist?
+ if (isset($this->aliases[$id])) {
+ $id = $this->aliases[$id];
+ }
+
+ if (isset($this->services[$id])) {
+ $arguments[$key] = $this->services[$id];
+ continue;
+ }
+
+ // Return the service.
+ $arguments[$key] = $this->get($id, $argument->invalidBehavior);
+
+ continue;
+ }
+ // Create private service.
+ elseif ($type == 'private_service') {
+ $id = $argument->id;
+
+ // Does the private service already exist.
+ if (isset($this->privateServices[$id])) {
+ $arguments[$key] = $this->privateServices[$id];
+ continue;
+ }
+
+ // Create the private service.
+ $arguments[$key] = $this->createService($argument->value, $id);
+ if ($argument->shared) {
+ $this->privateServices[$id] = $arguments[$key];
+ }
+
+ continue;
+ }
+ // Check for collection.
+ elseif ($type == 'collection') {
+ $value = $argument->value;
+
+ // Does this collection need resolving?
+ if ($argument->resolve) {
+ $arguments[$key] = $this->resolveServicesAndParameters($value);
+ }
+ else {
+ $arguments[$key] = $value;
+ }
+
+ continue;
+ }
+
+ if ($type !== NULL) {
+ throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $type));
+ }
+ }
+ }
+
+ return $arguments;
+ }
+
+ /**
+ * Provides alternatives for a given array and key.
+ *
+ * @param string $search_key
+ * The search key to get alternatives for.
+ * @param array $keys
+ * The search space to search for alternatives in.
+ *
+ * @return string[]
+ * An array of strings with suitable alternatives.
+ */
+ protected function getAlternatives($search_key, array $keys) {
+ $alternatives = array();
+ foreach ($keys as $key) {
+ $lev = levenshtein($search_key, $key);
+ if ($lev <= strlen($search_key) / 3 || strpos($key, $search_key) !== FALSE) {
+ $alternatives[] = $key;
+ }
+ }
+
+ return $alternatives;
+ }
+
+ /**
+ * Provides alternatives in case a service was not found.
+ *
+ * @param string $id
+ * The service to get alternatives for.
+ *
+ * @return string[]
+ * An array of strings with suitable alternatives.
+ */
+ protected function getServiceAlternatives($id) {
+ $all_service_keys = array_unique(array_merge(array_keys($this->services), array_keys($this->serviceDefinitions)));
+ return $this->getAlternatives($id, $all_service_keys);
+ }
+
+ /**
+ * Provides alternatives in case a parameter was not found.
+ *
+ * @param string $name
+ * The parameter to get alternatives for.
+ *
+ * @return string[]
+ * An array of strings with suitable alternatives.
+ */
+ protected function getParameterAlternatives($name) {
+ return $this->getAlternatives($name, array_keys($this->parameters));
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enterScope($name) {
+ throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function leaveScope($name) {
+ 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 supported by Drupal 8.", __FUNCTION__));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasScope($name) {
+ throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isScopeActive($name) {
+ throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
+ }
+
+ /**
+ * Gets all defined service IDs.
+ *
+ * @return array
+ * An array of all defined service IDs.
+ */
+ public function getServiceIds() {
+ return array_keys($this->serviceDefinitions + $this->services);
+ }
+
+}
diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
new file mode 100644
index 0000000..23290b6
--- /dev/null
+++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
@@ -0,0 +1,513 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.
+ */
+
+namespace Drupal\Component\DependencyInjection\Dumper;
+
+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();
+ $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.");
+ }
+ }
+
+ 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 = hash('sha1', serialize($service_definition));
+ $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.
+ $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;
+ }
+
+}
diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/PhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/PhpArrayDumper.php
new file mode 100644
index 0000000..4932dd7
--- /dev/null
+++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/PhpArrayDumper.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.
+ */
+
+namespace Drupal\Component\DependencyInjection\Dumper;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * PhpArrayDumper dumps a service container as a PHP array.
+ *
+ * The format of this dumper is a human-readable serialized PHP array, which is
+ * very similar to the YAML based format, but based on PHP arrays instead of
+ * YAML strings.
+ *
+ * It is human-readable, for a machine-optimized version based on this one see
+ * \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.
+ *
+ * @see \Drupal\Component\DependencyInjection\PhpArrayContainer
+ */
+class PhpArrayDumper extends OptimizedPhpArrayDumper {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getArray() {
+ $this->serialize = FALSE;
+ return parent::getArray();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function dumpCollection($collection, &$resolve = FALSE) {
+ $code = array();
+
+ foreach ($collection as $key => $value) {
+ if (is_array($value)) {
+ $code[$key] = $this->dumpCollection($value);
+ }
+ else {
+ $code[$key] = $this->dumpValue($value);
+ }
+ }
+
+ return $code;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ return '@?' . $id;
+ }
+
+ return '@' . $id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getParameterCall($name) {
+ return '%' . $name . '%';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function supportsMachineFormat() {
+ return FALSE;
+ }
+
+}
diff --git a/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php b/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php
new file mode 100644
index 0000000..53c33d9
--- /dev/null
+++ b/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Component\DependencyInjection\PhpArrayContainer.
+ */
+
+namespace Drupal\Component\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+
+/**
+ * Provides a container optimized for Drupal's needs.
+ *
+ * This container implementation is compatible with the default Symfony
+ * dependency injection container and similar to the Symfony ContainerBuilder
+ * class, but optimized for speed.
+ *
+ * It is based on a human-readable PHP array container definition with a
+ * structure very similar to the YAML container definition.
+ *
+ * @see \Drupal\Component\DependencyInjection\Container
+ * @see \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper
+ * @see \Drupal\Component\DependencyInjection\DependencySerializationTrait
+ *
+ * @ingroup container
+ */
+class PhpArrayContainer extends Container {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(array $container_definition = array()) {
+ if (isset($container_definition['machine_format']) && $container_definition['machine_format'] === TRUE) {
+ throw new InvalidArgumentException('The machine-optimized format is not supported by this class. Use a human-readable format instead, e.g. as produced by \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.');
+ }
+
+ // Do not call the parent's constructor as it would bail on the
+ // machine-optimized 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();
+ $this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE;
+
+ // Register the service_container with itself.
+ $this->services['service_container'] = $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function createService(array $definition, $id) {
+ // This method is a verbatim copy of
+ // \Drupal\Component\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 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();
+ if (isset($definition['arguments'])) {
+ $arguments = $this->resolveServicesAndParameters($definition['arguments']);
+ }
+
+ if (isset($definition['file'])) {
+ $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file'])));
+ require_once $file;
+ }
+
+ if (isset($definition['factory'])) {
+ $factory = $definition['factory'];
+ if (is_array($factory)) {
+ $factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1]));
+ }
+ elseif (!is_string($factory)) {
+ throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
+ }
+
+ $service = call_user_func_array($factory, $arguments);
+ }
+ else {
+ $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
+ // reflection is noticeably slow.
+ switch ($length) {
+ case 0:
+ $service = new $class();
+ break;
+
+ case 1:
+ $service = new $class($arguments[0]);
+ break;
+
+ case 2:
+ $service = new $class($arguments[0], $arguments[1]);
+ break;
+
+ case 3:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2]);
+ break;
+
+ case 4:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
+ break;
+
+ case 5:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
+ break;
+
+ case 6:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
+ break;
+
+ case 7:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
+ break;
+
+ case 8:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
+ break;
+
+ case 9:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
+ break;
+
+ case 10:
+ $service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
+ break;
+
+ default:
+ $r = new \ReflectionClass($class);
+ $service = $r->newInstanceArgs($arguments);
+ break;
+ }
+ }
+
+ // Share the service if it is public.
+ if (!isset($definition['public']) || $definition['public'] !== FALSE) {
+ // Forward compatibility fix for Symfony 2.8 update.
+ if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
+ $this->services[$id] = $service;
+ }
+ }
+
+ if (isset($definition['calls'])) {
+ foreach ($definition['calls'] as $call) {
+ $method = $call[0];
+ $arguments = array();
+ if (!empty($call[1])) {
+ $arguments = $call[1];
+ $arguments = $this->resolveServicesAndParameters($arguments);
+ }
+ call_user_func_array(array($service, $method), $arguments);
+ }
+ }
+
+ if (isset($definition['properties'])) {
+ $definition['properties'] = $this->resolveServicesAndParameters($definition['properties']);
+ foreach ($definition['properties'] as $key => $value) {
+ $service->{$key} = $value;
+ }
+ }
+
+ if (isset($definition['configurator'])) {
+ $callable = $definition['configurator'];
+ if (is_array($callable)) {
+ $callable = $this->resolveServicesAndParameters($callable);
+ }
+
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service)));
+ }
+
+ call_user_func($callable, $service);
+ }
+
+ return $service;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function resolveServicesAndParameters($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.
+ // - The depth of the tree representing the arguments is not known in
+ // advance, so it needs to be fully traversed recursively.
+ foreach ($arguments as $key => $argument) {
+ if ($argument instanceof \stdClass) {
+ $type = $argument->type;
+
+ // 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 an argument, which does not have an 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\Component\DependecyInjection\Dumper\OptimizedPhpArrayDumper::getPrivateServiceCall
+ if ($type == 'private_service') {
+ $id = $argument->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 a private service from a service definition.
+ $arguments[$key] = $this->createService($argument->value, $id);
+ if (!empty($argument->shared)) {
+ $this->privateServices[$id] = $arguments[$key];
+ }
+
+ continue;
+ }
+
+ if ($type !== NULL) {
+ throw new InvalidArgumentException("Undefined type '$type' while resolving parameters and services.");
+ }
+ }
+
+ if (is_array($argument)) {
+ $arguments[$key] = $this->resolveServicesAndParameters($argument);
+ continue;
+ }
+
+ if (!is_string($argument)) {
+ continue;
+ }
+
+ // Resolve parameters.
+ if ($argument[0] === '%') {
+ $name = substr($argument, 1, -1);
+ if (!isset($this->parameters[$name])) {
+ $arguments[$key] = $this->getParameter($name);
+ // This can never be reached as getParameter() throws an Exception,
+ // because we already checked that the parameter is not set above.
+ }
+ $argument = $this->parameters[$name];
+ $arguments[$key] = $argument;
+ }
+
+ // Resolve services.
+ if ($argument[0] === '@') {
+ $id = substr($argument, 1);
+ $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
+ if ($id[0] === '?') {
+ $id = substr($id, 1);
+ $invalid_behavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
+ }
+ if (isset($this->services[$id])) {
+ $arguments[$key] = $this->services[$id];
+ }
+ else {
+ $arguments[$key] = $this->get($id, $invalid_behavior);
+ }
+ }
+ }
+
+ return $arguments;
+ }
+
+}
diff --git a/core/lib/Drupal/Component/DependencyInjection/composer.json b/core/lib/Drupal/Component/DependencyInjection/composer.json
new file mode 100644
index 0000000..7e5e666
--- /dev/null
+++ b/core/lib/Drupal/Component/DependencyInjection/composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "drupal/core-dependency-injection",
+ "description": "Dependency Injection container optimized for Drupal's needs.",
+ "keywords": ["drupal", "dependency injection"],
+ "type": "library",
+ "homepage": "https://www.drupal.org/project/drupal",
+ "license": "GPL-2.0+",
+ "support": {
+ "issues": "https://www.drupal.org/project/issues/drupal",
+ "irc": "irc://irc.freenode.net/drupal-contribute",
+ "source": "https://www.drupal.org/project/drupal/git-instructions"
+ },
+ "autoload": {
+ "psr-4": {
+ "Drupal\\Component\\DependencyInjection\\": ""
+ }
+ }
+}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Container.php b/core/lib/Drupal/Core/DependencyInjection/Container.php
index 4e28d54..b4cac53 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Container.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Container.php
@@ -7,17 +7,18 @@
namespace Drupal\Core\DependencyInjection;
-use Symfony\Component\DependencyInjection\Container as SymfonyContainer;
+use Drupal\Component\DependencyInjection\Container as DrupalContainer;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
- * Extends the symfony container to set the service ID on the created object.
+ * Extends the Drupal container to set the service ID on the created object.
*/
-class Container extends SymfonyContainer {
+class Container extends DrupalContainer {
/**
* {@inheritdoc}
*/
- public function set($id, $service, $scope = SymfonyContainer::SCOPE_CONTAINER) {
+ public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) {
parent::set($id, $service, $scope);
// Ensure that the _serviceId property is set on synthetic services as well.
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 89a77e1..8ae5924 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -23,12 +23,10 @@ use Drupal\Core\File\MimeType\MimeTypeGuesser;
use Drupal\Core\Http\TrustedHostsRequestFactory;
use Drupal\Core\Language\Language;
use Drupal\Core\PageCache\RequestPolicyInterface;
-use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Site\Settings;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
-use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -54,6 +52,55 @@ use Symfony\Component\Routing\Route;
class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
/**
+ * Holds the class used for dumping the container to a PHP array.
+ *
+ * In combination with swapping the container class this is useful to e.g.
+ * dump to the human-readable PHP array format to debug the container
+ * definition in an easier way.
+ *
+ * @var string
+ */
+ protected $phpArrayDumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
+
+ /**
+ * Holds the default bootstrap container definition.
+ *
+ * @var array
+ */
+ protected $defaultBootstrapContainerDefinition = [
+ 'parameters' => [],
+ 'services' => [
+ 'database' => [
+ 'class' => 'Drupal\Core\Database\Connection',
+ 'factory' => 'Drupal\Core\Database\Database::getConnection',
+ 'arguments' => ['default'],
+ ],
+ 'cache.container' => [
+ 'class' => 'Drupal\Core\Cache\DatabaseBackend',
+ 'arguments' => ['@database', '@cache_tags_provider.container', 'container'],
+ ],
+ 'cache_tags_provider.container' => [
+ 'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum',
+ 'arguments' => ['@database'],
+ ],
+ ],
+ ];
+
+ /**
+ * Holds the class used for instantiating the bootstrap container.
+ *
+ * @var string
+ */
+ protected $bootstrapContainerClass = '\Drupal\Component\DependencyInjection\PhpArrayContainer';
+
+ /**
+ * Holds the bootstrap container.
+ *
+ * @var \Symfony\Component\DependencyInjection\ContainerInterface
+ */
+ protected $bootstrapContainer;
+
+ /**
* Holds the container instance.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
@@ -98,13 +145,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
protected $moduleData = array();
/**
- * PHP code storage object to use for the compiled container.
- *
- * @var \Drupal\Component\PhpStorage\PhpStorageInterface
- */
- protected $storage;
-
- /**
* The class loader object.
*
* @var \Composer\Autoload\ClassLoader
@@ -397,6 +437,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
FileCacheFactory::setConfiguration($configuration);
FileCacheFactory::setPrefix(Settings::getApcuPrefix('file_cache', $this->root));
+ $this->bootstrapContainer = new $this->bootstrapContainerClass(Settings::get('bootstrap_container_definition', $this->defaultBootstrapContainerDefinition));
+
// Initialize the container.
$this->initializeContainer();
@@ -449,6 +491,19 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
/**
* {@inheritdoc}
*/
+ public function getCachedContainerDefinition() {
+ $cache = $this->bootstrapContainer->get('cache.container')->get($this->getContainerCacheKey());
+
+ if ($cache) {
+ return $cache->data;
+ }
+
+ return NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function loadLegacyIncludes() {
require_once $this->root . '/core/includes/common.inc';
require_once $this->root . '/core/includes/database.inc';
@@ -706,24 +761,14 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
/**
- * Returns the classname based on environment.
+ * Returns the container cache key based on the environment.
*
* @return string
- * The class name.
+ * The cache key used for the service container.
*/
- protected function getClassName() {
- $parts = array('service_container', $this->environment, hash('crc32b', \Drupal::VERSION . Settings::get('deployment_identifier')));
- return implode('_', $parts);
- }
-
- /**
- * Returns the container class namespace based on the environment.
- *
- * @return string
- * The class name.
- */
- protected function getClassNamespace() {
- return 'Drupal\\Core\\DependencyInjection\\Container\\' . $this->environment;
+ protected function getContainerCacheKey() {
+ $parts = array('service_container', $this->environment, \Drupal::VERSION, Settings::get('deployment_identifier'));
+ return implode(':', $parts);
}
/**
@@ -766,32 +811,38 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
// boot the container injected via setContainer().
// @see \Drupal\KernelTests\KernelTestBase::setUp()
if (isset($this->container) && !$this->booted) {
- $container = $this->container;
+ $container = $this->container;
}
// If the module list hasn't already been set in updateModules and we are
- // not forcing a rebuild, then try and load the container from the disk.
+ // not forcing a rebuild, then try and load the container from the cache.
if (empty($this->moduleList) && !$this->containerNeedsRebuild) {
- $fully_qualified_class_name = '\\' . $this->getClassNamespace() . '\\' . $this->getClassName();
-
- // First, try to load from storage.
- if (!class_exists($fully_qualified_class_name, FALSE)) {
- $this->storage()->load($this->getClassName() . '.php');
- }
- // If the load succeeded or the class already existed, use it.
- if (class_exists($fully_qualified_class_name, FALSE)) {
- $container = new $fully_qualified_class_name;
- }
+ $container_definition = $this->getCachedContainerDefinition();
}
- // If there is still no container, build a new one from scratch.
- if (!isset($container)) {
+ // If there is no container and no cached container definition, build a new
+ // one from scratch.
+ if (!isset($container) && !isset($container_definition)) {
$container = $this->compileContainer();
+
+ // Only dump the container if dumping is allowed. This is useful for
+ // KernelTestBase, which never wants to use the real container, but always
+ // the container builder.
+ if ($this->allowDumping) {
+ $dumper = new $this->phpArrayDumperClass($container);
+ $container_definition = $dumper->getArray();
+ }
}
// The container was rebuilt successfully.
$this->containerNeedsRebuild = FALSE;
+ // Only create a new class if we have a container definition.
+ if (isset($container_definition)) {
+ $class = Settings::get('container_base_class', '\Drupal\Core\DependencyInjection\Container');
+ $container = new $class($container_definition);
+ }
+
$this->attachSynthetic($container);
$this->container = $container;
@@ -816,9 +867,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
\Drupal::setContainer($this->container);
// If needs dumping flag was set, dump the container.
- $base_class = Settings::get('container_base_class', '\Drupal\Core\DependencyInjection\Container');
- if ($this->containerNeedsDumping && !$this->dumpDrupalContainer($this->container, $base_class)) {
- $this->container->get('logger.factory')->get('DrupalKernel')->notice('Container cannot be written to disk');
+ if ($this->containerNeedsDumping && !$this->cacheDrupalContainer($container_definition)) {
+ $this->container->get('logger.factory')->get('DrupalKernel')->notice('Container cannot be saved to cache.');
}
return $this->container;
@@ -1034,9 +1084,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
return;
}
- // Also wipe the PHP Storage caches, so that the container is rebuilt
- // for the next request.
- $this->storage()->deleteAll();
+ // Also remove the container definition from the cache backend.
+ $this->bootstrapContainer->get('cache.container')->deleteAll();
}
/**
@@ -1194,35 +1243,28 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
/**
- * Dumps the service container to PHP code in the config directory.
- *
- * This method is based on the dumpContainer method in the parent class, but
- * that method is reliant on the Config component which we do not use here.
+ * Stores the container definition in a cache.
*
- * @param ContainerBuilder $container
- * The service container.
- * @param string $baseClass
- * The name of the container's base class
+ * @param array $container_definition
+ * The container definition to cache.
*
* @return bool
- * TRUE if the container was successfully dumped to disk.
+ * TRUE if the container was successfully cached.
*/
- protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) {
- if (!$this->storage()->writeable()) {
- return FALSE;
+ protected function cacheDrupalContainer(array $container_definition) {
+ $saved = TRUE;
+ try {
+ $this->bootstrapContainer->get('cache.container')->set($this->getContainerCacheKey(), $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;
}
- // Cache the container.
- $dumper = new PhpDumper($container);
- $class = $this->getClassName();
- $namespace = $this->getClassNamespace();
- $content = $dumper->dump([
- 'class' => $class,
- 'base_class' => $baseClass,
- 'namespace' => $namespace,
- ]);
- return $this->storage()->save($class . '.php', $content);
- }
+ return $saved;
+ }
/**
* Gets a http kernel from the container
@@ -1234,18 +1276,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
/**
- * Gets the PHP code storage object to use for the compiled container.
- *
- * @return \Drupal\Component\PhpStorage\PhpStorageInterface
- */
- protected function storage() {
- if (!isset($this->storage)) {
- $this->storage = PhpStorageFactory::get('service_container');
- }
- return $this->storage;
- }
-
- /**
* Returns the active configuration storage to use during building the container.
*
* @return \Drupal\Core\Config\StorageInterface
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index 12c32f6..3149694 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -59,6 +59,16 @@ interface DrupalKernelInterface extends HttpKernelInterface, ContainerAwareInter
public function getContainer();
/**
+ * Returns the cached container definition - if any.
+ *
+ * This also allows inspecting a built container for debugging purposes.
+ *
+ * @return array|NULL
+ * The cached container definition or NULL if not found in cache.
+ */
+ public function getCachedContainerDefinition();
+
+ /**
* Set the current site path.
*
* @param string $path
diff --git a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
index a4424ea..e8e35a6 100644
--- a/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
+++ b/core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
@@ -55,13 +55,11 @@ class DrupalKernelTest extends KernelTestBase {
* A request object to use in booting the kernel.
* @param array $modules_enabled
* A list of modules to enable on the kernel.
- * @param bool $read_only
- * Build the kernel in a read only state.
*
* @return \Drupal\Core\DrupalKernel
* New kernel for testing.
*/
- protected function getTestKernel(Request $request, array $modules_enabled = NULL, $read_only = FALSE) {
+ protected function getTestKernel(Request $request, array $modules_enabled = NULL) {
// Manually create kernel to avoid replacing settings.
$class_loader = require DRUPAL_ROOT . '/autoload.php';
$kernel = DrupalKernel::createFromRequest($request, $class_loader, 'testing');
@@ -72,11 +70,6 @@ class DrupalKernelTest extends KernelTestBase {
}
$kernel->boot();
- if ($read_only) {
- $php_storage = Settings::get('php_storage');
- $php_storage['service_container']['class'] = 'Drupal\Component\PhpStorage\FileReadOnlyStorage';
- $this->settingsSet('php_storage', $php_storage);
- }
return $kernel;
}
@@ -98,24 +91,19 @@ class DrupalKernelTest extends KernelTestBase {
$kernel = $this->getTestKernel($request);
$container = $kernel->getContainer();
$refClass = new \ReflectionClass($container);
- $is_compiled_container =
- $refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' &&
- !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
+ $is_compiled_container = !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_compiled_container);
// Verify that the list of modules is the same for the initial and the
// compiled container.
$module_list = array_keys($container->get('module_handler')->getModuleList());
$this->assertEqual(array_values($modules_enabled), $module_list);
- // Now use the read-only storage implementation, simulating a "production"
- // environment.
- $container = $this->getTestKernel($request, NULL, TRUE)
+ // Get the container another time, simulating a "production" environment.
+ $container = $this->getTestKernel($request, NULL)
->getContainer();
$refClass = new \ReflectionClass($container);
- $is_compiled_container =
- $refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' &&
- !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
+ $is_compiled_container = !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
$this->assertTrue($is_compiled_container);
// Verify that the list of modules is the same for the initial and the
@@ -137,16 +125,16 @@ class DrupalKernelTest extends KernelTestBase {
// Add another module so that we can test that the new module's bundle is
// registered to the new container.
$modules_enabled['service_provider_test'] = 'service_provider_test';
- $this->getTestKernel($request, $modules_enabled, TRUE);
+ $this->getTestKernel($request, $modules_enabled);
- // Instantiate it a second time and we should still get a ContainerBuilder
- // class because we are using the read-only PHP storage.
- $kernel = $this->getTestKernel($request, $modules_enabled, TRUE);
+ // Instantiate it a second time and we should not get a ContainerBuilder
+ // class because we are loading the container definition from cache.
+ $kernel = $this->getTestKernel($request, $modules_enabled);
$container = $kernel->getContainer();
$refClass = new \ReflectionClass($container);
$is_container_builder = $refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
- $this->assertTrue($is_container_builder, 'Container is a builder');
+ $this->assertFalse($is_container_builder, 'Container is not a builder');
// Assert that the new module's bundle was registered to the new container.
$this->assertTrue($container->has('service_provider_test_class'), 'Container has test service');
diff --git a/core/modules/system/src/Tests/ServiceProvider/ServiceProviderTest.php b/core/modules/system/src/Tests/ServiceProvider/ServiceProviderTest.php
index 9b67692..ef2fb79 100644
--- a/core/modules/system/src/Tests/ServiceProvider/ServiceProviderTest.php
+++ b/core/modules/system/src/Tests/ServiceProvider/ServiceProviderTest.php
@@ -7,14 +7,14 @@
namespace Drupal\system\Tests\ServiceProvider;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\KernelTestBase;
/**
* Tests service provider registration to the DIC.
*
* @group ServiceProvider
*/
-class ServiceProviderTest extends WebTestBase {
+class ServiceProviderTest extends KernelTestBase {
/**
* Modules to enable.
@@ -27,13 +27,9 @@ class ServiceProviderTest extends WebTestBase {
* Tests that services provided by module service providers get registered to the DIC.
*/
function testServiceProviderRegistration() {
- $this->assertTrue(\Drupal::getContainer()->getDefinition('file.usage')->getClass() == 'Drupal\\service_provider_test\\TestFileUsage', 'Class has been changed');
+ $definition = $this->container->getDefinition('file.usage');
+ $this->assertTrue($definition->getClass() == 'Drupal\\service_provider_test\\TestFileUsage', 'Class has been changed');
$this->assertTrue(\Drupal::hasService('service_provider_test_class'), 'The service_provider_test_class service has been registered to the DIC');
- // The event subscriber method in the test class calls drupal_set_message with
- // a message saying it has fired. This will fire on every page request so it
- // should show up on the front page.
- $this->drupalGet('');
- $this->assertText(t('The service_provider_test event subscriber fired!'), 'The service_provider_test event subscriber fired');
}
/**
diff --git a/core/modules/system/src/Tests/ServiceProvider/ServiceProviderWebTest.php b/core/modules/system/src/Tests/ServiceProvider/ServiceProviderWebTest.php
new file mode 100644
index 0000000..8a6d3bc
--- /dev/null
+++ b/core/modules/system/src/Tests/ServiceProvider/ServiceProviderWebTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\ServiceProvider\ServiceProviderWebTest.
+ */
+
+namespace Drupal\system\Tests\ServiceProvider;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests service provider registration to the DIC.
+ *
+ * @group ServiceProvider
+ */
+class ServiceProviderWebTest extends WebTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('file', 'service_provider_test');
+
+ /**
+ * Tests that module service providers get registered to the DIC.
+ *
+ * Also tests that services provided by module service providers get
+ * registered to the DIC.
+ */
+ public function testServiceProviderRegistrationIntegration() {
+ $this->assertTrue(\Drupal::hasService('service_provider_test_class'), 'The service_provider_test_class service has been registered to the DIC');
+ // The event subscriber method in the test class calls drupal_set_message()
+ // with a message saying it has fired. This will fire on every page request
+ // so it should show up on the front page.
+ $this->drupalGet('');
+ $this->assertText(t('The service_provider_test event subscriber fired!'), 'The service_provider_test event subscriber fired');
+ }
+
+}
diff --git a/core/modules/system/src/Tests/System/UncaughtExceptionTest.php b/core/modules/system/src/Tests/System/UncaughtExceptionTest.php
index 81cb76a..3460125 100644
--- a/core/modules/system/src/Tests/System/UncaughtExceptionTest.php
+++ b/core/modules/system/src/Tests/System/UncaughtExceptionTest.php
@@ -170,14 +170,7 @@ class UncaughtExceptionTest extends WebTestBase {
'required' => TRUE,
];
$this->writeSettings($settings);
-
- // Need to rebuild the container, so the dumped container can be tested
- // and not the container builder.
- \Drupal::service('kernel')->rebuildContainer();
-
- // Ensure that we don't use the now broken generated container on the test
- // process.
- \Drupal::setContainer($this->container);
+ \Drupal::service('kernel')->invalidateContainer();
$this->expectedExceptionMessage = 'Argument 1 passed to Drupal\system\Tests\Bootstrap\ErrorContainer::Drupal\system\Tests\Bootstrap\{closur';
$this->drupalGet('');
@@ -196,14 +189,7 @@ class UncaughtExceptionTest extends WebTestBase {
'required' => TRUE,
];
$this->writeSettings($settings);
-
- // Need to rebuild the container, so the dumped container can be tested
- // and not the container builder.
- \Drupal::service('kernel')->rebuildContainer();
-
- // Ensure that we don't use the now broken generated container on the test
- // process.
- \Drupal::setContainer($this->container);
+ \Drupal::service('kernel')->invalidateContainer();
$this->expectedExceptionMessage = 'Thrown exception during Container::get';
$this->drupalGet('');
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php
new file mode 100644
index 0000000..21f6f5f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php
@@ -0,0 +1,1188 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\DependencyInjection\ContainerTest.
+ */
+
+namespace Drupal\Tests\Component\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\LogicException;
+use Prophecy\Argument;
+
+/**
+ * @coversDefaultClass \Drupal\Component\DependencyInjection\Container
+ * @group DependencyInjection
+ */
+class ContainerTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * The tested container.
+ *
+ * @var \Symfony\Component\DependencyInjection\ContainerInterface
+ */
+ protected $container;
+
+ /**
+ * The container definition used for the test.
+ *
+ * @var array
+ */
+ protected $containerDefinition;
+
+ /**
+ * The container class to be tested.
+ *
+ * @var bool
+ */
+ protected $containerClass;
+
+ /**
+ * Whether the container uses the machine-optimized format or not.
+ *
+ * @var bool
+ */
+ protected $machineFormat;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ $this->machineFormat = TRUE;
+ $this->containerClass = '\Drupal\Component\DependencyInjection\Container';
+ $this->containerDefinition = $this->getMockContainerDefinition();
+ $this->container = new $this->containerClass($this->containerDefinition);
+ }
+
+ /**
+ * Tests that passing a non-supported format throws an InvalidArgumentException.
+ *
+ * @covers ::__construct
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testConstruct() {
+ $container_definition = $this->getMockContainerDefinition();
+ $container_definition['machine_format'] = !$this->machineFormat;
+ $container = new $this->containerClass($container_definition);
+ }
+
+ /**
+ * Tests that Container::getParameter() works properly.
+ *
+ * @covers ::getParameter
+ */
+ public function testGetParameter() {
+ $this->assertEquals($this->containerDefinition['parameters']['some_config'], $this->container->getParameter('some_config'), 'Container parameter matches for %some_config%.');
+ $this->assertEquals($this->containerDefinition['parameters']['some_other_config'], $this->container->getParameter('some_other_config'), 'Container parameter matches for %some_other_config%.');
+ }
+
+ /**
+ * Tests that Container::getParameter() works properly for non-existing
+ * parameters.
+ *
+ * @covers ::getParameter
+ * @covers ::getParameterAlternatives
+ * @covers ::getAlternatives
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException
+ */
+ public function testGetParameterIfNotFound() {
+ $this->container->getParameter('parameter_that_does_not_exist');
+ }
+
+ /**
+ * Tests that Container::getParameter() works properly for NULL parameters.
+ *
+ * @covers ::getParameter
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException
+ */
+ public function testGetParameterIfNotFoundBecauseNull() {
+ $this->container->getParameter(NULL);
+ }
+
+ /**
+ * Tests that Container::hasParameter() works properly.
+ *
+ * @covers ::hasParameter
+ */
+ public function testHasParameter() {
+ $this->assertTrue($this->container->hasParameter('some_config'), 'Container parameters include %some_config%.');
+ $this->assertFalse($this->container->hasParameter('some_config_not_exists'), 'Container parameters do not include %some_config_not_exists%.');
+ }
+
+ /**
+ * Tests that Container::setParameter() in an unfrozen case works properly.
+ *
+ * @covers ::setParameter
+ */
+ public function testSetParameterWithUnfrozenContainer() {
+ $container_definition = $this->containerDefinition;
+ $container_definition['frozen'] = FALSE;
+ $this->container = new $this->containerClass($container_definition);
+ $this->container->setParameter('some_config', 'new_value');
+ $this->assertEquals('new_value', $this->container->getParameter('some_config'), 'Container parameters can be set.');
+ }
+
+ /**
+ * Tests that Container::setParameter() in a frozen case works properly.
+ *
+ * @covers ::setParameter
+ *
+ * @expectedException LogicException
+ */
+ public function testSetParameterWithFrozenContainer() {
+ $this->container = new $this->containerClass($this->containerDefinition);
+ $this->container->setParameter('some_config', 'new_value');
+ }
+
+ /**
+ * Tests that Container::get() works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGet() {
+ $container = $this->container->get('service_container');
+ $this->assertSame($this->container, $container, 'Container can be retrieved from itself.');
+
+ // Retrieve services of the container.
+ $other_service_class = $this->containerDefinition['services']['other.service']['class'];
+ $other_service = $this->container->get('other.service');
+ $this->assertInstanceOf($other_service_class, $other_service, 'other.service has the right class.');
+
+ $some_parameter = $this->containerDefinition['parameters']['some_config'];
+ $some_other_parameter = $this->containerDefinition['parameters']['some_other_config'];
+
+ $service = $this->container->get('service.provider');
+
+ $this->assertEquals($other_service, $service->getSomeOtherService(), '@other.service was injected via constructor.');
+ $this->assertEquals($some_parameter, $service->getSomeParameter(), '%some_config% was injected via constructor.');
+ $this->assertEquals($this->container, $service->getContainer(), 'Container was injected via setter injection.');
+ $this->assertEquals($some_other_parameter, $service->getSomeOtherParameter(), '%some_other_config% was injected via setter injection.');
+ $this->assertEquals($service->_someProperty, 'foo', 'Service has added properties.');
+ }
+
+ /**
+ * Tests that Container::get() for non-shared services works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForNonSharedService() {
+ $service = $this->container->get('non_shared_service');
+ $service2 = $this->container->get('non_shared_service');
+
+ $this->assertNotSame($service, $service2, 'Non shared services are always re-instantiated.');
+ }
+
+ /**
+ * Tests that Container::get() works properly for class from parameters.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForClassFromParameter() {
+ $container_definition = $this->containerDefinition;
+ $container_definition['frozen'] = FALSE;
+ $container = new $this->containerClass($container_definition);
+
+ $other_service_class = $this->containerDefinition['parameters']['some_parameter_class'];
+ $other_service = $container->get('other.service_class_from_parameter');
+ $this->assertInstanceOf($other_service_class, $other_service, 'other.service_class_from_parameter has the right class.');
+ }
+
+ /**
+ * Tests that Container::set() works properly.
+ *
+ * @covers ::set
+ */
+ public function testSet() {
+ $this->assertNull($this->container->get('new_id', ContainerInterface::NULL_ON_INVALID_REFERENCE));
+ $mock_service = new MockService();
+ $this->container->set('new_id', $mock_service);
+
+ $this->assertSame($mock_service, $this->container->get('new_id'), 'A manual set service works as expected.');
+ }
+
+ /**
+ * Tests that Container::has() works properly.
+ *
+ * @covers ::has
+ */
+ public function testHas() {
+ $this->assertTrue($this->container->has('other.service'));
+ $this->assertFalse($this->container->has('another.service'));
+
+ // Set the service manually, ensure that its also respected.
+ $mock_service = new MockService();
+ $this->container->set('another.service', $mock_service);
+ $this->assertTrue($this->container->has('another.service'));
+ }
+
+ /**
+ * Tests that Container::get() for circular dependencies works properly.
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForCircularServices() {
+ $this->container->get('circular_dependency');
+ }
+
+ /**
+ * Tests that Container::get() for non-existent services works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::getAlternatives
+ * @covers ::getServiceAlternatives
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
+ */
+ public function testGetForNonExistantService() {
+ $this->container->get('service_not_exists');
+ }
+
+ /**
+ * Tests that Container::get() for a serialized definition works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForSerializedServiceDefinition() {
+ $container_definition = $this->containerDefinition;
+ $container_definition['services']['other.service'] = serialize($container_definition['services']['other.service']);
+ $container = new $this->containerClass($container_definition);
+
+ // Retrieve services of the container.
+ $other_service_class = $this->containerDefinition['services']['other.service']['class'];
+ $other_service = $container->get('other.service');
+ $this->assertInstanceOf($other_service_class, $other_service, 'other.service has the right class.');
+
+ $service = $container->get('service.provider');
+ $this->assertEquals($other_service, $service->getSomeOtherService(), '@other.service was injected via constructor.');
+ }
+
+ /**
+ * Tests that Container::get() for non-existent parameters works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testGetForNonExistantParameterDependency() {
+ $service = $this->container->get('service_parameter_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE);
+ $this->assertNull($service, 'Service is NULL.');
+ }
+
+ /**
+ * Tests Container::get() with an exception due to missing parameter on the second call.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testGetForParameterDependencyWithExceptionOnSecondCall() {
+ $service = $this->container->get('service_parameter_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE);
+ $this->assertNull($service, 'Service is NULL.');
+
+ // Reset the service.
+ $this->container->set('service_parameter_not_exists', NULL);
+ $this->container->get('service_parameter_not_exists');
+ }
+
+ /**
+ * Tests that Container::get() for non-existent parameters works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testGetForNonExistantParameterDependencyWithException() {
+ $this->container->get('service_parameter_not_exists');
+ }
+
+ /**
+ * Tests that Container::get() for non-existent dependencies works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testGetForNonExistantServiceDependency() {
+ $service = $this->container->get('service_dependency_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE);
+ $this->assertNull($service, 'Service is NULL.');
+ }
+
+ /**
+ * Tests that Container::get() for non-existent dependencies works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ * @covers ::getAlternatives
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
+ */
+ public function testGetForNonExistantServiceDependencyWithException() {
+ $this->container->get('service_dependency_not_exists');
+ }
+
+ /**
+ * Tests that Container::get() for non-existent services works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForNonExistantServiceWhenUsingNull() {
+ $this->assertNull($this->container->get('service_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'Not found service does not throw exception.');
+ }
+
+ /**
+ * Tests that Container::get() for NULL service works properly.
+ * @covers ::get
+ * @covers ::createService
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
+ */
+ public function testGetForNonExistantNULLService() {
+ $this->container->get(NULL);
+ }
+
+ /**
+ * Tests multiple Container::get() calls for non-existing dependencies work.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForNonExistantServiceMultipleTimes() {
+ $container = new $this->containerClass();
+
+ $this->assertNull($container->get('service_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'Not found service does not throw exception.');
+ $this->assertNull($container->get('service_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'Not found service does not throw exception on second call.');
+ }
+
+ /**
+ * Tests multiple Container::get() calls with exception on the second time.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::getAlternatives
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
+ */
+ public function testGetForNonExistantServiceWithExceptionOnSecondCall() {
+ $this->assertNull($this->container->get('service_not_exists', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'Not found service does nto throw exception.');
+ $this->container->get('service_not_exists');
+ }
+
+ /**
+ * Tests that Container::get() for aliased services works properly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForAliasedService() {
+ $service = $this->container->get('service.provider');
+ $aliased_service = $this->container->get('service.provider_alias');
+ $this->assertSame($service, $aliased_service);
+ }
+
+ /**
+ * Tests that Container::get() for synthetic services works - if defined.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForSyntheticService() {
+ $synthetic_service = new \stdClass();
+ $this->container->set('synthetic', $synthetic_service);
+ $test_service = $this->container->get('synthetic');
+ $this->assertSame($synthetic_service, $test_service);
+ }
+
+ /**
+ * Tests that Container::get() for synthetic services throws an Exception if not defined.
+ *
+ * @covers ::get
+ * @covers ::createService
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ */
+ public function testGetForSyntheticServiceWithException() {
+ $this->container->get('synthetic');
+ }
+
+ /**
+ * Tests that Container::get() for services with file includes works.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetWithFileInclude() {
+ $file_service = $this->container->get('container_test_file_service_test');
+ $this->assertTrue(function_exists('container_test_file_service_test_service_function'));
+ $this->assertEquals('Hello Container', container_test_file_service_test_service_function());
+ }
+
+ /**
+ * Tests that Container::get() for various arguments lengths works.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testGetForInstantiationWithVariousArgumentLengths() {
+ $args = array();
+ for ($i=0; $i < 12; $i++) {
+ $instantiation_service = $this->container->get('service_test_instantiation_'. $i);
+ $this->assertEquals($args, $instantiation_service->getArguments());
+ $args[] = 'arg_' . $i;
+ }
+ }
+
+ /**
+ * Tests that Container::get() for wrong factories works correctly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ */
+ public function testGetForWrongFactory() {
+ $this->container->get('wrong_factory');
+ }
+
+ /**
+ * Tests Container::get() for factories via services (Symfony 2.7.0).
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForFactoryService() {
+ $factory_service = $this->container->get('factory_service');
+ $factory_service_class = $this->container->getParameter('factory_service_class');
+ $this->assertInstanceOf($factory_service_class, $factory_service);
+ }
+
+ /**
+ * Tests that Container::get() for factories via class works (Symfony 2.7.0).
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForFactoryClass() {
+ $service = $this->container->get('service.provider');
+ $factory_service= $this->container->get('factory_class');
+
+ $this->assertInstanceOf(get_class($service), $factory_service);
+ $this->assertEquals('bar', $factory_service->getSomeParameter(), 'Correct parameter was passed via the factory class instantiation.');
+ $this->assertEquals($this->container, $factory_service->getContainer(), 'Container was injected via setter injection.');
+ }
+
+ /**
+ * Tests that Container::get() for configurable services throws an Exception.
+ *
+ * @covers ::get
+ * @covers ::createService
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testGetForConfiguratorWithException() {
+ $this->container->get('configurable_service_exception');
+ }
+
+ /**
+ * Tests that Container::get() for configurable services works.
+ *
+ * @covers ::get
+ * @covers ::createService
+ */
+ public function testGetForConfigurator() {
+ $container = $this->container;
+
+ // Setup a configurator.
+ $configurator = $this->prophesize('\Drupal\Tests\Component\DependencyInjection\MockConfiguratorInterface');
+ $configurator->configureService(Argument::type('object'))
+ ->shouldBeCalled(1)
+ ->will(function($args) use ($container) {
+ $args[0]->setContainer($container);
+ });
+ $container->set('configurator', $configurator->reveal());
+
+ // Test that the configurator worked.
+ $service = $container->get('configurable_service');
+ $this->assertSame($container, $service->getContainer(), 'Container was injected via configurator.');
+ }
+
+ /**
+ * Tests that private services work correctly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testResolveServicesAndParametersForPrivateService() {
+ $service = $this->container->get('service_using_private');
+ $private_service = $service->getSomeOtherService();
+ $this->assertEquals($private_service->getSomeParameter(), 'really_private_lama', 'Private was found successfully.');
+
+ // Test that sharing the same private services works.
+ $service = $this->container->get('another_service_using_private');
+ $another_private_service = $service->getSomeOtherService();
+ $this->assertNotSame($private_service, $another_private_service, 'Private service is not shared.');
+ $this->assertEquals($private_service->getSomeParameter(), 'really_private_lama', 'Private was found successfully.');
+ }
+
+ /**
+ * Tests that private service sharing works correctly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testResolveServicesAndParametersForSharedPrivateService() {
+ $service = $this->container->get('service_using_shared_private');
+ $private_service = $service->getSomeOtherService();
+ $this->assertEquals($private_service->getSomeParameter(), 'really_private_lama', 'Private was found successfully.');
+
+ // Test that sharing the same private services works.
+ $service = $this->container->get('another_service_using_shared_private');
+ $same_private_service = $service->getSomeOtherService();
+ $this->assertSame($private_service, $same_private_service, 'Private service is shared.');
+ $this->assertEquals($private_service->getSomeParameter(), 'really_private_lama', 'Private was found successfully.');
+ }
+
+ /**
+ * Tests that services with an array of arguments work correctly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testResolveServicesAndParametersForArgumentsUsingDeepArray() {
+ $service = $this->container->get('service_using_array');
+ $other_service = $this->container->get('other.service');
+ $this->assertEquals($other_service, $service->getSomeOtherService(), '@other.service was injected via constructor.');
+ }
+
+ /**
+ * Tests that services that are optional work correctly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testResolveServicesAndParametersForOptionalServiceDependencies() {
+ $service = $this->container->get('service_with_optional_dependency');
+ $this->assertNull($service->getSomeOtherService(), 'other service was NULL was expected.');
+ }
+
+ /**
+ * Tests that an invalid argument throw an Exception.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testResolveServicesAndParametersForInvalidArgument() {
+ $this->container->get('invalid_argument_service');
+ }
+
+ /**
+ * Tests that invalid arguments throw an Exception.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testResolveServicesAndParametersForInvalidArguments() {
+ // In case the machine-optimized format is not used, we need to simulate the
+ // test failure.
+ if (!$this->machineFormat) {
+ throw new InvalidArgumentException('Simulating the test failure.');
+ }
+ $this->container->get('invalid_arguments_service');
+ }
+
+ /**
+ * Tests that a parameter that points to a service works correctly.
+ *
+ * @covers ::get
+ * @covers ::createService
+ * @covers ::resolveServicesAndParameters
+ */
+ public function testResolveServicesAndParametersForServiceInstantiatedFromParameter() {
+ $service = $this->container->get('service.provider');
+ $test_service = $this->container->get('service_with_parameter_service');
+ $this->assertSame($service, $test_service->getSomeOtherService(), 'Service was passed via parameter.');
+ }
+
+ /**
+ * Tests that Container::initialized works correctly.
+ *
+ * @covers ::initialized
+ */
+ public function testInitialized() {
+ $this->assertFalse($this->container->initialized('late.service'), 'Late service is not initialized.');
+ $this->container->get('late.service');
+ $this->assertTrue($this->container->initialized('late.service'), 'Late service is initialized after it was retrieved once.');
+ }
+
+ /**
+ * Tests that Container::initialized works correctly for aliases.
+ *
+ * @covers ::initialized
+ */
+ public function testInitializedForAliases() {
+ $this->assertFalse($this->container->initialized('late.service_alias'), 'Late service is not initialized.');
+ $this->container->get('late.service');
+ $this->assertTrue($this->container->initialized('late.service_alias'), 'Late service is initialized after it was retrieved once.');
+ }
+
+ /**
+ * Tests that unsupported methods throw an Exception.
+ *
+ * @covers ::enterScope
+ * @covers ::leaveScope
+ * @covers ::addScope
+ * @covers ::hasScope
+ * @covers ::isScopeActive
+ *
+ * @expectedException \BadMethodCallException
+ *
+ * @dataProvider scopeExceptionTestProvider
+ */
+ public function testScopeFunctionsWithException($method, $argument) {
+ $callable = array(
+ $this->container,
+ $method,
+ );
+
+ $callable($argument);
+ }
+
+ /**
+ * Data provider for scopeExceptionTestProvider().
+ *
+ * @return array[]
+ * Returns per data set an array with:
+ * - method name to call
+ * - argument to pass
+ */
+ public function scopeExceptionTestProvider() {
+ $scope = $this->prophesize('\Symfony\Component\DependencyInjection\ScopeInterface')->reveal();
+ return array(
+ array('enterScope', 'test_scope'),
+ array('leaveScope', 'test_scope'),
+ array('hasScope', 'test_scope'),
+ array('isScopeActive', 'test_scope'),
+ array('addScope', $scope),
+ );
+ }
+
+ /**
+ * Tests that Container::getServiceIds() works properly.
+ *
+ * @covers ::getServiceIds
+ */
+ public function testGetServiceIds() {
+ $service_definition_keys = array_keys($this->containerDefinition['services']);
+ $this->assertEquals($service_definition_keys, $this->container->getServiceIds(), 'Retrieved service IDs match definition.');
+
+ $mock_service = new MockService();
+ $this->container->set('bar', $mock_service);
+ $this->container->set('service.provider', $mock_service);
+ $service_definition_keys[] = 'bar';
+
+ $this->assertEquals($service_definition_keys, $this->container->getServiceIds(), 'Retrieved service IDs match definition after setting new services.');
+ }
+
+ /**
+ * Gets a mock container definition.
+ *
+ * @return array
+ * Associated array with parameters and services.
+ */
+ protected function getMockContainerDefinition() {
+ $fake_service = new \stdClass();
+ $parameters = array();
+ $parameters['some_parameter_class'] = get_class($fake_service);
+ $parameters['some_private_config'] = 'really_private_lama';
+ $parameters['some_config'] = 'foo';
+ $parameters['some_other_config'] = 'lama';
+ $parameters['factory_service_class'] = get_class($fake_service);
+ // Also test alias resolving.
+ $parameters['service_from_parameter'] = $this->getServiceCall('service.provider_alias');
+
+ $services = array();
+ $services['service_container'] = array(
+ 'class' => '\Drupal\service_container\DependencyInjection\Container',
+ );
+ $services['other.service'] = array(
+ 'class' => get_class($fake_service),
+ );
+
+ $services['non_shared_service'] = array(
+ 'class' => get_class($fake_service),
+ 'shared' => FALSE,
+ );
+
+ $services['other.service_class_from_parameter'] = array(
+ 'class' => $this->getParameterCall('some_parameter_class'),
+ );
+ $services['late.service'] = array(
+ 'class' => get_class($fake_service),
+ );
+ $services['service.provider'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getServiceCall('other.service'),
+ $this->getParameterCall('some_config'),
+ )),
+ 'properties' => $this->getCollection(array('_someProperty' => 'foo')),
+ 'calls' => array(
+ array('setContainer', $this->getCollection(array(
+ $this->getServiceCall('service_container'),
+ ))),
+ array('setOtherConfigParameter', $this->getCollection(array(
+ $this->getParameterCall('some_other_config'),
+ ))),
+ ),
+ 'priority' => 0,
+ );
+
+ // Test private services.
+ $private_service = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getServiceCall('other.service'),
+ $this->getParameterCall('some_private_config'),
+ )),
+ 'public' => FALSE,
+ );
+
+ $services['service_using_private'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getPrivateServiceCall(NULL, $private_service),
+ $this->getParameterCall('some_config'),
+ )),
+ );
+ $services['another_service_using_private'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getPrivateServiceCall(NULL, $private_service),
+ $this->getParameterCall('some_config'),
+ )),
+ );
+
+ // Test shared private services.
+ $id = 'private_service_shared_1';
+
+ $services['service_using_shared_private'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getPrivateServiceCall($id, $private_service, TRUE),
+ $this->getParameterCall('some_config'),
+ )),
+ );
+ $services['another_service_using_shared_private'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getPrivateServiceCall($id, $private_service, TRUE),
+ $this->getParameterCall('some_config'),
+ )),
+ );
+
+ // Tests service with invalid argument.
+ $services['invalid_argument_service'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ 1, // Test passing non-strings, too.
+ (object) array(
+ 'type' => 'invalid',
+ ),
+ )),
+ );
+
+ $services['invalid_arguments_service'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => (object) array(
+ 'type' => 'invalid',
+ ),
+ );
+
+ // Test service that needs deep-traversal.
+ $services['service_using_array'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getCollection(array(
+ $this->getServiceCall('other.service'),
+ )),
+ $this->getParameterCall('some_private_config'),
+ )),
+ );
+
+ $services['service_with_optional_dependency'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getServiceCall('service.does_not_exist', ContainerInterface::NULL_ON_INVALID_REFERENCE),
+ $this->getParameterCall('some_private_config'),
+ )),
+
+ );
+
+ $services['factory_service'] = array(
+ 'class' => '\Drupal\service_container\ServiceContainer\ControllerInterface',
+ 'factory' => array(
+ $this->getServiceCall('service.provider'),
+ 'getFactoryMethod',
+ ),
+ 'arguments' => $this->getCollection(array(
+ $this->getParameterCall('factory_service_class'),
+ )),
+ );
+ $services['factory_class'] = array(
+ 'class' => '\Drupal\service_container\ServiceContainer\ControllerInterface',
+ 'factory' => '\Drupal\Tests\Component\DependencyInjection\MockService::getFactoryMethod',
+ 'arguments' => array(
+ '\Drupal\Tests\Component\DependencyInjection\MockService',
+ array(NULL, 'bar'),
+ ),
+ 'calls' => array(
+ array('setContainer', $this->getCollection(array(
+ $this->getServiceCall('service_container'),
+ ))),
+ ),
+ );
+
+ $services['wrong_factory'] = array(
+ 'class' => '\Drupal\service_container\ServiceContainer\ControllerInterface',
+ 'factory' => (object) array('I am not a factory, but I pretend to be.'),
+ );
+
+ $services['circular_dependency'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getServiceCall('circular_dependency'),
+ )),
+ );
+ $services['synthetic'] = array(
+ 'synthetic' => TRUE,
+ );
+ // The file could have been named as a .php file. The reason it is a .data
+ // file is that SimpleTest tries to load it. SimpleTest does not like such
+ // fixtures and hence we use a neutral name like .data.
+ $services['container_test_file_service_test'] = array(
+ 'class' => '\stdClass',
+ 'file' => __DIR__ . '/Fixture/container_test_file_service_test_service_function.data',
+ );
+
+ // Test multiple arguments.
+ $args = array();
+ for ($i=0; $i < 12; $i++) {
+ $services['service_test_instantiation_' . $i] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockInstantiationService',
+ // Also test a collection that does not need resolving.
+ 'arguments' => $this->getCollection($args, FALSE),
+ );
+ $args[] = 'arg_' . $i;
+ }
+
+ $services['service_parameter_not_exists'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getServiceCall('service.provider'),
+ $this->getParameterCall('not_exists'),
+ )),
+ );
+ $services['service_dependency_not_exists'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getServiceCall('service_not_exists'),
+ $this->getParameterCall('some_config'),
+ )),
+ );
+
+ $services['service_with_parameter_service'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => $this->getCollection(array(
+ $this->getParameterCall('service_from_parameter'),
+ // Also test deep collections that don't need resolving.
+ $this->getCollection(array(
+ 1,
+ ), FALSE),
+ )),
+ );
+
+ // To ensure getAlternatives() finds something.
+ $services['service_not_exists_similar'] = array(
+ 'synthetic' => TRUE,
+ );
+
+ // Test configurator.
+ $services['configurator'] = array(
+ 'synthetic' => TRUE,
+ );
+ $services['configurable_service'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => array(),
+ 'configurator' => array(
+ $this->getServiceCall('configurator'),
+ 'configureService'
+ ),
+ );
+ $services['configurable_service_exception'] = array(
+ 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService',
+ 'arguments' => array(),
+ 'configurator' => 'configurator_service_test_does_not_exist',
+ );
+
+ $aliases = array();
+ $aliases['service.provider_alias'] = 'service.provider';
+ $aliases['late.service_alias'] = 'late.service';
+
+ return array(
+ 'aliases' => $aliases,
+ 'parameters' => $parameters,
+ 'services' => $services,
+ 'frozen' => TRUE,
+ 'machine_format' => $this->machineFormat,
+ );
+ }
+
+ /**
+ * Helper function to return a service definition.
+ */
+ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ return (object) array(
+ 'type' => 'service',
+ 'id' => $id,
+ 'invalidBehavior' => $invalid_behavior,
+ );
+ }
+
+ /**
+ * Helper function to return a service definition.
+ */
+ protected function getParameterCall($name) {
+ return (object) array(
+ 'type' => 'parameter',
+ 'name' => $name,
+ );
+ }
+
+ /**
+ * Helper function to return a private service definition.
+ */
+ protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
+ if (!$id) {
+ $hash = sha1(serialize($service_definition));
+ $id = 'private__' . $hash;
+ }
+ return (object) array(
+ 'type' => 'private_service',
+ 'id' => $id,
+ 'value' => $service_definition,
+ 'shared' => $shared,
+ );
+ }
+
+ /**
+ * Helper function to return a machine-optimized collection.
+ */
+ protected function getCollection($collection, $resolve = TRUE) {
+ return (object) array(
+ 'type' => 'collection',
+ 'value' => $collection,
+ 'resolve' => $resolve,
+ );
+ }
+
+}
+
+/**
+ * Helper interface to test Container::get() with configurator.
+ *
+ * @group DependencyInjection
+ */
+interface MockConfiguratorInterface {
+
+ /**
+ * Configures a service.
+ *
+ * @param object $service
+ * The service to configure.
+ */
+ public function configureService($service);
+
+}
+
+
+/**
+ * Helper class to test Container::get() method for varying number of parameters.
+ *
+ * @group DependencyInjection
+ */
+class MockInstantiationService {
+
+ /**
+ * @var mixed[]
+ */
+ protected $arguments;
+
+ /**
+ * Construct a mock instantiation service.
+ */
+ public function __construct() {
+ $this->arguments = func_get_args();
+ }
+
+ /**
+ * Return arguments injected into the service.
+ *
+ * @return mixed[]
+ * Return the passed arguments.
+ */
+ public function getArguments() {
+ return $this->arguments;
+ }
+
+}
+
+
+/**
+ * Helper class to test Container::get() method.
+ *
+ * @group DependencyInjection
+ */
+class MockService {
+
+ /**
+ * @var ContainerInterface
+ */
+ protected $container;
+
+ /**
+ * @var object
+ */
+ protected $someOtherService;
+
+ /**
+ * @var string
+ */
+ protected $someParameter;
+
+ /**
+ * @var string
+ */
+ protected $someOtherParameter;
+
+ /**
+ * Constructs a MockService object.
+ *
+ * @param object $some_other_service
+ * (optional) Another injected service.
+ * @param string $some_parameter
+ * (optional) An injected parameter.
+ */
+ public function __construct($some_other_service = NULL, $some_parameter = NULL) {
+ if (is_array($some_other_service)) {
+ $some_other_service = $some_other_service[0];
+ }
+ $this->someOtherService = $some_other_service;
+ $this->someParameter = $some_parameter;
+ }
+
+ /**
+ * Sets the container object.
+ *
+ * @param ContainerInterface $container
+ * The container to inject via setter injection.
+ */
+ public function setContainer(ContainerInterface $container) {
+ $this->container = $container;
+ }
+
+ /**
+ * Gets the container object.
+ *
+ * @return ContainerInterface
+ * The internally set container.
+ */
+ public function getContainer() {
+ return $this->container;
+ }
+
+ /**
+ * Gets the someOtherService object.
+ *
+ * @return object
+ * The injected service.
+ */
+ public function getSomeOtherService() {
+ return $this->someOtherService;
+ }
+
+ /**
+ * Gets the someParameter property.
+ *
+ * @return string
+ * The injected parameter.
+ */
+ public function getSomeParameter() {
+ return $this->someParameter;
+ }
+
+ /**
+ * Sets the someOtherParameter property.
+ *
+ * @param string $some_other_parameter
+ * The setter injected parameter.
+ */
+ public function setOtherConfigParameter($some_other_parameter) {
+ $this->someOtherParameter = $some_other_parameter;
+ }
+
+ /**
+ * Gets the someOtherParameter property.
+ *
+ * @return string
+ * The injected parameter.
+ */
+ public function getSomeOtherParameter() {
+ return $this->someOtherParameter;
+ }
+
+ /**
+ * Provides a factory method to get a service.
+ *
+ * @param string $class
+ * The class name of the class to instantiate
+ * @param array $arguments
+ * (optional) Arguments to pass to the new class.
+ *
+ * @return object
+ * The instantiated service object.
+ */
+ public static function getFactoryMethod($class, $arguments = array()) {
+ $r = new \ReflectionClass($class);
+ $service = ($r->getConstructor() === NULL) ? $r->newInstance() : $r->newInstanceArgs($arguments);
+
+ return $service;
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
new file mode 100644
index 0000000..e127547
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
@@ -0,0 +1,621 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest.
+ */
+
+namespace Drupal\Tests\Component\DependencyInjection\Dumper {
+
+ use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\Reference;
+ use Symfony\Component\DependencyInjection\Parameter;
+ use Symfony\Component\ExpressionLanguage\Expression;
+ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+
+ /**
+ * @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
+ * @group DependencyInjection
+ */
+ class OptimizedPhpArrayDumperTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * The container builder instance.
+ *
+ * @var \Symfony\Component\DependencyInjection\ContainerBuilder
+ */
+ protected $containerBuilder;
+
+ /**
+ * The definition for the container to build in tests.
+ *
+ * @var array
+ */
+ protected $containerDefinition;
+
+ /**
+ * Whether the dumper uses the machine-optimized format or not.
+ *
+ * @var bool
+ */
+ protected $machineFormat = TRUE;
+
+ /**
+ * Stores the dumper class to use.
+ *
+ * @var string
+ */
+ protected $dumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
+
+ /**
+ * The dumper instance.
+ *
+ * @var \Symfony\Component\DependencyInjection\DumperDumperInterface
+ */
+ protected $dumper;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ // Setup a mock container builder.
+ $this->containerBuilder = $this->prophesize('\Symfony\Component\DependencyInjection\ContainerBuilder');
+ $this->containerBuilder->getAliases()->willReturn(array());
+ $this->containerBuilder->getParameterBag()->willReturn(new ParameterBag());
+ $this->containerBuilder->getDefinitions()->willReturn(NULL);
+ $this->containerBuilder->isFrozen()->willReturn(TRUE);
+
+ $definition = array();
+ $definition['aliases'] = array();
+ $definition['parameters'] = array();
+ $definition['services'] = array();
+ $definition['frozen'] = TRUE;
+ $definition['machine_format'] = $this->machineFormat;
+
+ $this->containerDefinition = $definition;
+
+ // Create the dumper.
+ $this->dumper = new $this->dumperClass($this->containerBuilder->reveal());
+ }
+
+ /**
+ * Tests that an empty container works properly.
+ *
+ * @covers ::dump
+ * @covers ::getArray
+ * @covers ::supportsMachineFormat
+ */
+ public function testDumpForEmptyContainer() {
+ $serialized_definition = $this->dumper->dump();
+ $this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
+ }
+
+ /**
+ * Tests that alias processing works properly.
+ *
+ * @covers ::getAliases
+ *
+ * @dataProvider getAliasesDataProvider
+ */
+ public function testGetAliases($aliases, $definition_aliases) {
+ $this->containerDefinition['aliases'] = $definition_aliases;
+ $this->containerBuilder->getAliases()->willReturn($aliases);
+ $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
+ }
+
+ /**
+ * Data provider for testGetAliases().
+ *
+ * @return array[]
+ * Returns data-set elements with:
+ * - aliases as returned by ContainerBuilder.
+ * - aliases as expected in the container definition.
+ */
+ public function getAliasesDataProvider() {
+ return array(
+ array(array(), array()),
+ array(
+ array('foo' => 'foo.alias'),
+ array('foo' => 'foo.alias'),
+ ),
+ array(
+ array('foo' => 'foo.alias', 'foo.alias' => 'foo.alias.alias'),
+ array('foo' => 'foo.alias.alias', 'foo.alias' => 'foo.alias.alias'),
+ ),
+ );
+ }
+
+ /**
+ * Tests that parameter processing works properly.
+ *
+ * @covers ::getParameters
+ * @covers ::prepareParameters
+ * @covers ::escape
+ * @covers ::dumpValue
+ * @covers ::getReferenceCall
+ *
+ * @dataProvider getParametersDataProvider
+ */
+ public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
+ $this->containerDefinition['parameters'] = $definition_parameters;
+ $this->containerDefinition['frozen'] = $is_frozen;
+
+ $parameter_bag = new ParameterBag($parameters);
+ $this->containerBuilder->getParameterBag()->willReturn($parameter_bag);
+ $this->containerBuilder->isFrozen()->willReturn($is_frozen);
+
+ if (isset($parameters['reference'])) {
+ $definition = new Definition('\stdClass');
+ $this->containerBuilder->getDefinition('referenced_service')->willReturn($definition);
+ }
+
+ $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
+ }
+
+ /**
+ * Data provider for testGetParameters().
+ *
+ * @return array[]
+ * Returns data-set elements with:
+ * - parameters as returned by ContainerBuilder.
+ * - parameters as expected in the container definition.
+ * - frozen value
+ */
+ public function getParametersDataProvider() {
+ return array(
+ array(array(), array(), TRUE),
+ array(
+ array('foo' => 'value_foo'),
+ array('foo' => 'value_foo'),
+ TRUE,
+ ),
+ array(
+ array('foo' => array('llama' => 'yes')),
+ array('foo' => array('llama' => 'yes')),
+ TRUE,
+ ),
+ array(
+ array('foo' => '%llama%', 'llama' => 'yes'),
+ array('foo' => '%%llama%%', 'llama' => 'yes'),
+ TRUE,
+ ),
+ array(
+ array('foo' => '%llama%', 'llama' => 'yes'),
+ array('foo' => '%llama%', 'llama' => 'yes'),
+ FALSE,
+ ),
+ array(
+ array('reference' => new Reference('referenced_service')),
+ array('reference' => $this->getServiceCall('referenced_service')),
+ TRUE,
+ ),
+ );
+ }
+
+ /**
+ * Tests that service processing works properly.
+ *
+ * @covers ::getServiceDefinitions
+ * @covers ::getServiceDefinition
+ * @covers ::dumpMethodCalls
+ * @covers ::dumpCollection
+ * @covers ::dumpCallable
+ * @covers ::dumpValue
+ * @covers ::getPrivateServiceCall
+ * @covers ::getReferenceCall
+ * @covers ::getServiceCall
+ * @covers ::getParameterCall
+ *
+ * @dataProvider getDefinitionsDataProvider
+ */
+ public function testGetServiceDefinitions($services, $definition_services) {
+ $this->containerDefinition['services'] = $definition_services;
+
+ $this->containerBuilder->getDefinitions()->willReturn($services);
+
+ $bar_definition = new Definition('\stdClass');
+ $this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
+
+ $private_definition = new Definition('\stdClass');
+ $private_definition->setPublic(FALSE);
+
+ $this->containerBuilder->getDefinition('private_definition')->willReturn($private_definition);
+
+ $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
+ }
+
+ /**
+ * Data provider for testGetServiceDefinitions().
+ *
+ * @return array[]
+ * Returns data-set elements with:
+ * - parameters as returned by ContainerBuilder.
+ * - parameters as expected in the container definition.
+ * - frozen value
+ */
+ public function getDefinitionsDataProvider() {
+ $base_service_definition = array(
+ 'class' => '\stdClass',
+ 'public' => TRUE,
+ 'file' => FALSE,
+ 'synthetic' => FALSE,
+ 'lazy' => FALSE,
+ 'arguments' => array(),
+ 'arguments_count' => 0,
+ 'properties' => array(),
+ 'calls' => array(),
+ 'scope' => ContainerInterface::SCOPE_CONTAINER,
+ 'shared' => TRUE,
+ 'factory' => FALSE,
+ 'configurator' => FALSE,
+ );
+
+ // Test basic flags.
+ $service_definitions[] = array() + $base_service_definition;
+
+ $service_definitions[] = array(
+ 'public' => FALSE,
+ ) + $base_service_definition;
+
+ $service_definitions[] = array(
+ 'file' => 'test_include.php',
+ ) + $base_service_definition;
+
+ $service_definitions[] = array(
+ 'synthetic' => TRUE,
+ ) + $base_service_definition;
+
+ $service_definitions[] = array(
+ 'lazy' => TRUE,
+ ) + $base_service_definition;
+
+ // Test a basic public Reference.
+ $service_definitions[] = array(
+ 'arguments' => array('foo', new Reference('bar')),
+ 'arguments_count' => 2,
+ 'arguments_expected' => $this->getCollection(array('foo', $this->getServiceCall('bar'))),
+ ) + $base_service_definition;
+
+ // Test a public reference that should not throw an Exception.
+ $reference = new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE);
+ $service_definitions[] = array(
+ 'arguments' => array($reference),
+ 'arguments_count' => 1,
+ 'arguments_expected' => $this->getCollection(array($this->getServiceCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE))),
+ ) + $base_service_definition;
+
+ // Test a private shared service, denoted by having a Reference.
+ $private_definition = array(
+ 'class' => '\stdClass',
+ 'public' => FALSE,
+ 'arguments_count' => 0,
+ );
+
+ $service_definitions[] = array(
+ 'arguments' => array('foo', new Reference('private_definition')),
+ 'arguments_count' => 2,
+ 'arguments_expected' => $this->getCollection(array(
+ 'foo',
+ $this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
+ )),
+ ) + $base_service_definition;
+
+ // Test a private non-shared service, denoted by having a Definition.
+ $private_definition_object = new Definition('\stdClass');
+ $private_definition_object->setPublic(FALSE);
+
+ $service_definitions[] = array(
+ 'arguments' => array('foo', $private_definition_object),
+ 'arguments_count' => 2,
+ 'arguments_expected' => $this->getCollection(array(
+ 'foo',
+ $this->getPrivateServiceCall(NULL, $private_definition),
+ )),
+ ) + $base_service_definition;
+
+ // Test a deep collection without a reference.
+ $service_definitions[] = array(
+ 'arguments' => array(array(array('foo'))),
+ 'arguments_count' => 1,
+ ) + $base_service_definition;
+
+ // Test a deep collection with a reference to resolve.
+ $service_definitions[] = array(
+ 'arguments' => array(array(new Reference('bar'))),
+ 'arguments_count' => 1,
+ 'arguments_expected' => $this->getCollection(array($this->getCollection(array($this->getServiceCall('bar'))))),
+ ) + $base_service_definition;
+
+ // Test a collection with a variable to resolve.
+ $service_definitions[] = array(
+ 'arguments' => array(new Parameter('llama_parameter')),
+ 'arguments_count' => 1,
+ 'arguments_expected' => $this->getCollection(array($this->getParameterCall('llama_parameter'))),
+ ) + $base_service_definition;
+
+ // Test objects that have _serviceId property.
+ $drupal_service = new \stdClass();
+ $drupal_service->_serviceId = 'bar';
+
+ $service_definitions[] = array(
+ 'arguments' => array($drupal_service),
+ 'arguments_count' => 1,
+ 'arguments_expected' => $this->getCollection(array($this->getServiceCall('bar'))),
+ ) + $base_service_definition;
+
+ // Test getMethodCalls.
+ $calls = array(
+ array('method', $this->getCollection(array())),
+ array('method2', $this->getCollection(array())),
+ );
+ $service_definitions[] = array(
+ 'calls' => $calls,
+ ) + $base_service_definition;
+
+ $service_definitions[] = array(
+ 'scope' => ContainerInterface::SCOPE_PROTOTYPE,
+ 'shared' => FALSE,
+ ) + $base_service_definition;
+
+ // Test factory.
+ $service_definitions[] = array(
+ 'factory' => array(new Reference('bar'), 'factoryMethod'),
+ 'factory_expected' => array($this->getServiceCall('bar'), 'factoryMethod'),
+ ) + $base_service_definition;
+
+ // Test invalid factory - needed to test deep dumpValue().
+ $service_definitions[] = array(
+ 'factory' => array(array('foo', 'llama'), 'factoryMethod'),
+ ) + $base_service_definition;
+
+ // Test properties.
+ $service_definitions[] = array(
+ 'properties' => array('_value' => 'llama'),
+ ) + $base_service_definition;
+
+ // Test configurator.
+ $service_definitions[] = array(
+ 'configurator' => array(new Reference('bar'), 'configureService'),
+ 'configurator_expected' => array($this->getServiceCall('bar'), 'configureService'),
+ ) + $base_service_definition;
+
+ $services_provided = array();
+ $services_provided[] = array(
+ array(),
+ array(),
+ );
+
+ foreach ($service_definitions as $service_definition) {
+ $definition = $this->prophesize('\Symfony\Component\DependencyInjection\Definition');
+ $definition->getClass()->willReturn($service_definition['class']);
+ $definition->isPublic()->willReturn($service_definition['public']);
+ $definition->getFile()->willReturn($service_definition['file']);
+ $definition->isSynthetic()->willReturn($service_definition['synthetic']);
+ $definition->isLazy()->willReturn($service_definition['lazy']);
+ $definition->getArguments()->willReturn($service_definition['arguments']);
+ $definition->getProperties()->willReturn($service_definition['properties']);
+ $definition->getMethodCalls()->willReturn($service_definition['calls']);
+ $definition->getScope()->willReturn($service_definition['scope']);
+ $definition->getDecoratedService()->willReturn(NULL);
+ $definition->getFactory()->willReturn($service_definition['factory']);
+ $definition->getConfigurator()->willReturn($service_definition['configurator']);
+
+ // Preserve order.
+ $filtered_service_definition = array();
+ foreach ($base_service_definition as $key => $value) {
+ $filtered_service_definition[$key] = $service_definition[$key];
+ unset($service_definition[$key]);
+
+ if ($key == 'class' || $key == 'arguments_count') {
+ continue;
+ }
+
+ if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
+ unset($filtered_service_definition[$key]);
+ }
+ }
+
+ // Add remaining properties.
+ $filtered_service_definition += $service_definition;
+
+ // Allow to set _expected values.
+ foreach (array('arguments', 'factory', 'configurator') as $key) {
+ $expected = $key . '_expected';
+ if (isset($filtered_service_definition[$expected])) {
+ $filtered_service_definition[$key] = $filtered_service_definition[$expected];
+ unset($filtered_service_definition[$expected]);
+ }
+ }
+
+ // Remove any remaining scope.
+ unset($filtered_service_definition['scope']);
+
+ if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
+ $services_provided[] = array(
+ array('foo_service' => $definition->reveal()),
+ array(),
+ );
+ continue;
+ }
+
+ $services_provided[] = array(
+ array('foo_service' => $definition->reveal()),
+ array('foo_service' => $this->serializeDefinition($filtered_service_definition)),
+ );
+ }
+
+ return $services_provided;
+ }
+
+ /**
+ * Helper function to serialize a definition.
+ *
+ * Used to override serialization.
+ */
+ protected function serializeDefinition(array $service_definition) {
+ return serialize($service_definition);
+ }
+
+ /**
+ * Helper function to return a service definition.
+ */
+ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ return (object) array(
+ 'type' => 'service',
+ 'id' => $id,
+ 'invalidBehavior' => $invalid_behavior,
+ );
+ }
+
+ /**
+ * Tests that the correct InvalidArgumentException is thrown for getScope().
+ *
+ * @covers ::getServiceDefinition
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testGetServiceDefinitionWithInvalidScope() {
+ $bar_definition = new Definition('\stdClass');
+ $bar_definition->setScope('foo_scope');
+ $services['bar'] = $bar_definition;
+
+ $this->containerBuilder->getDefinitions()->willReturn($services);
+ $this->dumper->getArray();
+ }
+
+ /**
+ * Tests that getDecoratedService() is unsupported.
+ *
+ * Tests that the correct InvalidArgumentException is thrown for
+ * getDecoratedService().
+ *
+ * @covers ::getServiceDefinition
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testGetServiceDefinitionForDecoratedService() {
+ $bar_definition = new Definition('\stdClass');
+ $bar_definition->setDecoratedService(new Reference('foo'));
+ $services['bar'] = $bar_definition;
+
+ $this->containerBuilder->getDefinitions()->willReturn($services);
+ $this->dumper->getArray();
+ }
+
+ /**
+ * Tests that the correct RuntimeException is thrown for expressions.
+ *
+ * @covers ::dumpValue
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ */
+ public function testGetServiceDefinitionForExpression() {
+ $expression = new Expression();
+
+ $bar_definition = new Definition('\stdClass');
+ $bar_definition->addArgument($expression);
+ $services['bar'] = $bar_definition;
+
+ $this->containerBuilder->getDefinitions()->willReturn($services);
+ $this->dumper->getArray();
+ }
+
+ /**
+ * Tests that the correct RuntimeException is thrown for dumping an object.
+ *
+ * @covers ::dumpValue
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ */
+ public function testGetServiceDefinitionForObject() {
+ $service = new \stdClass();
+
+ $bar_definition = new Definition('\stdClass');
+ $bar_definition->addArgument($service);
+ $services['bar'] = $bar_definition;
+
+ $this->containerBuilder->getDefinitions()->willReturn($services);
+ $this->dumper->getArray();
+ }
+
+ /**
+ * Tests that the correct RuntimeException is thrown for dumping a resource.
+ *
+ * @covers ::dumpValue
+ *
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ */
+ public function testGetServiceDefinitionForResource() {
+ $resource = fopen('php://memory', 'r');
+
+ $bar_definition = new Definition('\stdClass');
+ $bar_definition->addArgument($resource);
+ $services['bar'] = $bar_definition;
+
+ $this->containerBuilder->getDefinitions()->willReturn($services);
+ $this->dumper->getArray();
+ }
+
+ /**
+ * Helper function to return a private service definition.
+ */
+ protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
+ if (!$id) {
+ $hash = sha1(serialize($service_definition));
+ $id = 'private__' . $hash;
+ }
+ return (object) array(
+ 'type' => 'private_service',
+ 'id' => $id,
+ 'value' => $service_definition,
+ 'shared' => $shared,
+ );
+ }
+
+ /**
+ * Helper function to return a machine-optimized collection.
+ */
+ protected function getCollection($collection, $resolve = TRUE) {
+ return (object) array(
+ 'type' => 'collection',
+ 'value' => $collection,
+ 'resolve' => $resolve,
+ );
+ }
+
+ /**
+ * Helper function to return a parameter definition.
+ */
+ protected function getParameterCall($name) {
+ return (object) array(
+ 'type' => 'parameter',
+ 'name' => $name,
+ );
+ }
+
+ }
+
+}
+
+/**
+ * As Drupal Core does not ship with ExpressionLanguage component we need to
+ * define a dummy, else it cannot be tested.
+ */
+namespace Symfony\Component\ExpressionLanguage {
+ if (!class_exists('\Symfony\Component\ExpressionLanguage\Expression')) {
+ /**
+ * Dummy class to ensure non-existent Symfony component can be tested.
+ */
+ class Expression {
+
+ /**
+ * Gets the string representation of the expression.
+ */
+ public function __toString() {
+ return 'dummy_expression';
+ }
+
+ }
+ }
+}
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/PhpArrayDumperTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/PhpArrayDumperTest.php
new file mode 100644
index 0000000..55e9a5f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/PhpArrayDumperTest.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\DependencyInjection\Dumper\PhpArrayDumperTest.
+ */
+
+namespace Drupal\Tests\Component\DependencyInjection\Dumper;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper
+ * @group DependencyInjection
+ */
+class PhpArrayDumperTest extends OptimizedPhpArrayDumperTest {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ $this->machineFormat = FALSE;
+ $this->dumperClass = '\Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper';
+ parent::setUp();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function serializeDefinition(array $service_definition) {
+ return $service_definition;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ return sprintf('@?%s', $id);
+ }
+
+ return sprintf('@%s', $id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getParameterCall($name) {
+ return '%' . $name . '%';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getCollection($collection, $resolve = TRUE) {
+ return $collection;
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Fixture/container_test_file_service_test_service_function.data b/core/tests/Drupal/Tests/Component/DependencyInjection/Fixture/container_test_file_service_test_service_function.data
new file mode 100644
index 0000000..09960fe
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Fixture/container_test_file_service_test_service_function.data
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains a test function for container 'file' include testing.
+ */
+
+/**
+ * Test function for container testing.
+ *
+ * @return string
+ * A string just for testing.
+ */
+function container_test_file_service_test_service_function() {
+ return 'Hello Container';
+}
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php
new file mode 100644
index 0000000..8ad93b3
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/PhpArrayContainerTest.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\DependencyInjection\PhpArrayContainerTest.
+ */
+
+namespace Drupal\Tests\Component\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Exception\LogicException;
+
+/**
+ * @coversDefaultClass \Drupal\Component\DependencyInjection\PhpArrayContainer
+ * @group DependencyInjection
+ */
+class PhpArrayContainerTest extends ContainerTest {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ $this->machineFormat = FALSE;
+ $this->containerClass = '\Drupal\Component\DependencyInjection\PhpArrayContainer';
+ $this->containerDefinition = $this->getMockContainerDefinition();
+ $this->container = new $this->containerClass($this->containerDefinition);
+ }
+
+ /**
+ * Helper function to return a service definition.
+ */
+ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
+ return sprintf('@?%s', $id);
+ }
+
+ return sprintf('@%s', $id);
+ }
+
+ /**
+ * Helper function to return a service definition.
+ */
+ protected function getParameterCall($name) {
+ return '%' . $name . '%';
+ }
+
+ /**
+ * Helper function to return a machine-optimized '@notation'.
+ */
+ protected function getCollection($collection, $resolve = TRUE) {
+ return $collection;
+ }
+
+}