You are here

class PhpArrayContainer in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php \Drupal\Component\DependencyInjection\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

Expanded class hierarchy of PhpArrayContainer

See also

\Drupal\Component\DependencyInjection\Container

\Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper

\Drupal\Component\DependencyInjection\DependencySerializationTrait

Related topics

File

core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php, line 29
Contains \Drupal\Component\DependencyInjection\PhpArrayContainer.

Namespace

Drupal\Component\DependencyInjection
View source
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;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
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::addScope public function Adds a scope to the container. Overrides ContainerInterface::addScope
Container::enterScope public function Enters the given scope. Overrides ContainerInterface::enterScope
Container::get public function Gets a service. Overrides ContainerInterface::get 2
Container::getAlternatives protected function Provides alternatives for a given array and key.
Container::getParameter public function Gets a parameter. 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.
Container::has public function Returns true if the given service is defined. Overrides ContainerInterface::has
Container::hasParameter public function Checks if a parameter exists. Overrides ContainerInterface::hasParameter
Container::hasScope public function Whether this container has the given scope. Overrides ContainerInterface::hasScope
Container::initialized public function Check for whether or not a service has been initialized. Overrides IntrospectableContainerInterface::initialized
Container::isScopeActive public function Determines whether the given scope is currently active. Overrides ContainerInterface::isScopeActive
Container::leaveScope public function Leaves the current scope, and re-enters the parent scope. Overrides ContainerInterface::leaveScope
Container::set public function Sets a service. Overrides ContainerInterface::set 1
Container::setParameter public function Sets a parameter. Overrides ContainerInterface::setParameter
ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE constant
ContainerInterface::IGNORE_ON_INVALID_REFERENCE constant
ContainerInterface::NULL_ON_INVALID_REFERENCE constant
ContainerInterface::SCOPE_CONTAINER constant
ContainerInterface::SCOPE_PROTOTYPE 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