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…
-
Automatically discover and collect all services with the specified tag (here:
breadcrumb_builder) -
Validate that each handler implements the interface expected by the consuming service.
-
Sort all handlers by priority.
-
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.