Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

In Drupal 8, various services leverage the service container to operate with multiple, pluggable handlers.

The pluggable handlers are services themselves. Their service definitions are tagged with a name that allows the consuming "manager" service to identify and collect them (most often accompanied with a 'priority' tag attribute).

In the past, a custom service container CompilerPass class had to be written in order to implement the concept.

There is a new, centralized TaggedHandlersPass now, which centralizes the operation into a single CompilerPass that can be re-used for all consumer/manager services.

To use it, simply tag the consuming service as a 'service_collector':

  breadcrumb:
    class: Drupal\Core\Breadcrumb\BreadcrumbManager
    tags:
      - { name: service_collector, tag: breadcrumb_builder, call: addBuilder }

Doing so will make the service container…

  1. Automatically discover and collect all services with the specified tag (here: breadcrumb_builder)

  2. Validate that each handler implements the interface expected by the consuming service.

  3. Sort all handlers by priority.

  4. Add a method call for each handler to the consuming service definition, using the specified method (here: BreadcrumbManager::addBuilder())

For example, a corresponding service definition of a handler:

  book.breadcrumb:
    class: Drupal\book\BookBreadcrumbBuilder
    tags:
      - { name: breadcrumb_builder, priority: 10 }

More details are documented on TaggedHandlersPass::process().

Disambiguation

This mechanism allows a service to get multiple processor services injected, in order to establish an extensible architecture.

It differs from the factory pattern in that processors are not lazily instantiated on demand; the consuming service receives instances of all registered processors when it is instantiated. Unlike a factory service, the consuming service is not ContainerAware.

It differs from plugins in that all processors are explicitly registered by service providers (driven by declarative configuration in code); the mere availability of a processor (cf. plugin discovery) does not imply that a processor ought to be registered and used.

It differs from regular service definition arguments (constructor injection) in that a consuming service MAY allow further processors to be added dynamically at runtime. To help with this, if the called method has a second argument called $priority then the priority of the processor will be passed to the method.

Impacts: 
Module developers