Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. PhpArrayContainer.php

class PhpArrayContainer

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.

Hierarchy

  • class \Drupal\Component\DependencyInjection\Container implements \Drupal\Component\DependencyInjection\ContainerInterface, \Symfony\Contracts\Service\ResetInterface
    • class \Drupal\Component\DependencyInjection\PhpArrayContainer extends \Drupal\Component\DependencyInjection\Container

Expanded class hierarchy of PhpArrayContainer

See also

\Drupal\Component\DependencyInjection\Container

\Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper

\Drupal\Component\DependencyInjection\DependencySerializationTrait

Related topics

Services and Dependency Injection Container
Overview of the Dependency Injection Container and Services.

File

core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php, line 26

Namespace

Drupal\Component\DependencyInjection
View source
class PhpArrayContainer extends Container {
    
    /**
     * {@inheritdoc}
     */
    public function __construct(array $container_definition = []) {
        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 = $container_definition['aliases'] ?? [];
        $this->parameters = $container_definition['parameters'] ?? [];
        $this->serviceDefinitions = $container_definition['services'] ?? [];
        $this->frozen = $container_definition['frozen'] ?? FALSE;
    }
    
    /**
     * {@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 = [];
        if (isset($definition['arguments'])) {
            $arguments = $this->resolveServicesAndParameters($definition['arguments']);
        }
        if (isset($definition['file'])) {
            $file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters([
                $definition['file'],
            ]));
            require_once $file;
        }
        if (isset($definition['factory'])) {
            $factory = $definition['factory'];
            if (is_array($factory)) {
                $factory = $this->resolveServicesAndParameters([
                    $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([
                $definition['class'],
            ]));
            $service = new $class(...$arguments);
        }
        if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
            $this->services[$id] = $service;
        }
        if (isset($definition['calls'])) {
            foreach ($definition['calls'] as $call) {
                $method = $call[0];
                $arguments = [];
                if (!empty($call[1])) {
                    $arguments = $call[1];
                    $arguments = $this->resolveServicesAndParameters($arguments);
                }
                call_user_func_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\DependencyInjection\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;
                }
                elseif ($type == 'service_closure') {
                    $arguments[$key] = function () use ($argument) {
                        return $this->get($argument->id, $argument->invalidBehavior);
                    };
                    continue;
                }
                elseif ($type == 'raw') {
                    $arguments[$key] = $argument->value;
                    continue;
                }
                elseif ($type == 'iterator') {
                    $services = $argument->value;
                    $arguments[$key] = new RewindableGenerator(function () use ($services) {
                        foreach ($services as $key => $service) {
                            (yield $key => $this->resolveServicesAndParameters([
                                $service,
                            ])[0]);
                        }
                    }, count($services));
                    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;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
Container::$aliases protected property The aliases of the container.
Container::$frozen protected property Whether the container parameters can still be changed.
Container::$loading protected property The currently loading services.
Container::$parameters protected property The parameters of the container.
Container::$privateServices protected property The instantiated private services.
Container::$serviceDefinitions protected property The service definitions of the container.
Container::$services protected property The instantiated services.
Container::get public function @template B of self::*_REFERENCE Overrides ContainerInterface::get
Container::getAlternatives protected function Provides alternatives for a given array and key.
Container::getParameter public function Overrides ContainerInterface::getParameter
Container::getParameterAlternatives protected function Provides alternatives in case a parameter was not found.
Container::getServiceAlternatives protected function Provides alternatives in case a service was not found.
Container::getServiceIds public function Gets all defined service IDs. Overrides ContainerInterface::getServiceIds
Container::has public function Returns true if the container can return an entry for the given identifier.
Returns false otherwise.
Overrides ContainerInterface::has
Container::hasParameter public function Overrides ContainerInterface::hasParameter
Container::initialized public function Check for whether or not a service has been initialized. Overrides ContainerInterface::initialized
Container::reset public function Resets shared services from the container. Overrides ResetInterface::reset
Container::set public function Overrides ContainerInterface::set
Container::setParameter public function Overrides ContainerInterface::setParameter
Container::__clone private function Ensure that cloning doesn't work.
ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE public constant
ContainerInterface::IGNORE_ON_INVALID_REFERENCE public constant
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE public constant
ContainerInterface::NULL_ON_INVALID_REFERENCE public constant
ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE public constant
PhpArrayContainer::createService protected function Creates a service from a service definition. Overrides Container::createService
PhpArrayContainer::resolveServicesAndParameters protected function Resolves arguments that represent services or variables to the real values. Overrides Container::resolveServicesAndParameters
PhpArrayContainer::__construct public function Constructs a new Container instance. Overrides Container::__construct
RSS feed
Powered by Drupal