Controllers and forms aren't the only classes that need services — plugins (block plugins, queue workers, field formatters, and dozens of other plugin types) need them just as often, but plugins have their own container-aware interface instead of ControllerBase's create().
What you'll learn in this lesson
- Why plugins use
ContainerFactoryPluginInterfaceinstead of the pattern you've seen so far - Why a plugin's
create()and constructor both need three extra parameters controllers and forms don't - How to inject a service into a real queue worker plugin
A real service-injected plugin
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\State\StateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class ReportWorkerBase extends QueueWorkerBase implements ContainerFactoryPluginInterface {
protected $state;
protected $logger;
public function __construct(array $configuration, $plugin_id, $plugin_definition, StateInterface $state, LoggerChannelFactoryInterface $logger) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->state = $state;
$this->logger = $logger;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('state'),
$container->get('logger.factory')
);
}
}
Three extra parameters, every time
Compare this to a controller's create(ContainerInterface $container) — a plugin's signature is create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition). Those three extra parameters are how the plugin system passes along a specific plugin instance's own configuration (for a block, that might be its placement settings; for a queue worker, usually just plugin metadata). Your constructor must accept them too and forward them to parent::__construct() before adding your own injected services after them.
Implementing the interface is what makes injection possible
implements ContainerFactoryPluginInterface is the signal that tells Drupal's plugin manager "don't just instantiate me with new — call my create() method instead." Skip this interface, and the plugin manager falls back to plain instantiation, silently ignoring any create() method you wrote.
parent::__construct($configuration, $plugin_id, $plugin_definition) must come first inside your constructor, before you assign your own injected properties — QueueWorkerBase's own constructor needs those three values to set up its internal plugin state correctly.Quick check: you write a new block plugin, inject a service into its constructor, but forget to add
implements ContainerFactoryPluginInterfaceto the class declaration. What breaks? (Answer: the plugin manager instantiates your class with plainnew, calling the constructor with only$configuration, $plugin_id, $plugin_definition— your extra injected-service parameter is never supplied, causing an immediate "too few arguments" fatal error.)
Key takeaways
- Plugins use
ContainerFactoryPluginInterface, notControllerBase's pattern — implementing the interface is what activates container-based instantiation. - A plugin's
create()and constructor both need$configuration, $plugin_id, $plugin_definitionin addition to whatever services you inject. - Always call
parent::__construct($configuration, $plugin_id, $plugin_definition)first, before assigning your own injected properties.
Coming up next
You've now injected services into a service, a controller, and a plugin. The last piece of the puzzle is tagging — how a service can announce itself to Drupal core as "I want to participate in event dispatching" or "collect me into this other service's list," without anyone hardcoding a reference to it.