You are here

public function TaggedHandlersPass::process in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass::process()

Finds services tagged with 'service_collector', then finds all corresponding tagged services and adds a method call for each to the consuming/collecting service definition.

Supported 'service_collector' tag attributes:

  • tag: The tag name used by handler services to collect. Defaults to the service ID of the consumer.
  • call: The method name to call on the consumer service. Defaults to 'addHandler'. The called method receives two arguments:

    • The handler instance as first argument.
    • Optionally the handler's priority as second argument, if the method accepts a second parameter and its name is "priority". In any case, all handlers registered at compile time are sorted already.
  • required: Boolean indicating if at least one handler service is required. Defaults to FALSE.

Example (YAML):


tags:
  - { name: service_collector, tag: breadcrumb_builder, call: addBuilder }

Supported handler tag attributes:

  • priority: An integer denoting the priority of the handler. Defaults to 0.

Example (YAML):


tags:
  - { name: breadcrumb_builder, priority: 100 }

Throws

\Symfony\Component\DependencyInjection\Exception\LogicException If the method of a consumer service to be called does not type-hint an interface.

\Symfony\Component\DependencyInjection\Exception\LogicException If a tagged handler does not implement the required interface.

\Symfony\Component\DependencyInjection\Exception\LogicException If at least one tagged service is required but none are found.

Overrides CompilerPassInterface::process

File

core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php, line 82
Contains \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass.

Class

TaggedHandlersPass
Collects services to add/inject them into a consumer service.

Namespace

Drupal\Core\DependencyInjection\Compiler

Code

public function process(ContainerBuilder $container) {
  foreach ($container
    ->findTaggedServiceIds('service_collector') as $consumer_id => $passes) {
    foreach ($passes as $pass) {
      $tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id;
      $method_name = isset($pass['call']) ? $pass['call'] : 'addHandler';
      $required = isset($pass['required']) ? $pass['required'] : FALSE;

      // Determine parameters.
      $consumer = $container
        ->getDefinition($consumer_id);
      $method = new \ReflectionMethod($consumer
        ->getClass(), $method_name);
      $params = $method
        ->getParameters();
      $interface_pos = 0;
      $id_pos = NULL;
      $priority_pos = NULL;
      $extra_params = [];
      foreach ($params as $pos => $param) {
        if ($param
          ->getClass()) {
          $interface = $param
            ->getClass();
        }
        else {
          if ($param
            ->getName() === 'id') {
            $id_pos = $pos;
          }
          else {
            if ($param
              ->getName() === 'priority') {
              $priority_pos = $pos;
            }
            else {
              $extra_params[$param
                ->getName()] = $pos;
            }
          }
        }
      }

      // Determine the ID.
      if (!isset($interface)) {
        throw new LogicException(vsprintf("Service consumer '%s' class method %s::%s() has to type-hint an interface.", array(
          $consumer_id,
          $consumer
            ->getClass(),
          $method_name,
        )));
      }
      $interface = $interface
        ->getName();

      // Find all tagged handlers.
      $handlers = array();
      $extra_arguments = array();
      foreach ($container
        ->findTaggedServiceIds($tag) as $id => $attributes) {

        // Validate the interface.
        $handler = $container
          ->getDefinition($id);
        if (!is_subclass_of($handler
          ->getClass(), $interface)) {
          throw new LogicException("Service '{$id}' for consumer '{$consumer_id}' does not implement {$interface}.");
        }
        $handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;

        // Keep track of other tagged handlers arguments.
        foreach ($extra_params as $name => $pos) {
          $extra_arguments[$id][$pos] = isset($attributes[0][$name]) ? $attributes[0][$name] : $params[$pos]
            ->getDefaultValue();
        }
      }
      if (empty($handlers)) {
        if ($required) {
          throw new LogicException(sprintf("At least one service tagged with '%s' is required.", $tag));
        }
        continue;
      }

      // Sort all handlers by priority.
      arsort($handlers, SORT_NUMERIC);

      // Add a method call for each handler to the consumer service
      // definition.
      foreach ($handlers as $id => $priority) {
        $arguments = array();
        $arguments[$interface_pos] = new Reference($id);
        if (isset($priority_pos)) {
          $arguments[$priority_pos] = $priority;
        }
        if (isset($id_pos)) {
          $arguments[$id_pos] = $id;
        }

        // Add in extra arguments.
        if (isset($extra_arguments[$id])) {

          // Place extra arguments in their right positions.
          $arguments += $extra_arguments[$id];
        }

        // Sort the arguments by position.
        ksort($arguments);
        $consumer
          ->addMethodCall($method_name, $arguments);
      }
    }
  }
}