Creating your own Plugin Manager

Last updated on
8 March 2024

This documentation needs work. See "Help improve this page" in the sidebar.

The plugin manager is the central controlling class that defines how the plugins of a particular type will be discovered and instantiated. This class is called directly in any module wishing to invoke a plugin type.

This documentation will require an understanding of PSR-4.

Scaffolding for plugin managers and plugin types can be generated automatically with the following Drush command:

drush gen plugin 

Defining the Plugin Manager

There are only two requirements for defining a new plugin manager:

  1. You must define a discovery method.
  2. You must define a factory.

Everything else within the plugin system is situational and based upon your use case.

It is recommended to use the DefaultPluginManager as a base class, that defaults to Annotation based discovery with derivates, the container factory and has built-in support for caching by language and definition altering and processing.

Service definition

Plugin managers should be defined as services. It is considered best practice to prefix the service name with plugin.manager.

An example MODULE.services.yml, listing a single plugin manager service:

services:
  plugin.manager.archiver:
    class: Drupal\Core\Archiver\ArchiverManager
    parent: default_plugin_manager

See the end of this page for examples using this service. For more detail, see Services and dependency injection in Drupal 8.

Plugin manager class

namespace Drupal\Core\Archiver;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;

/**
 * Provides an Archiver plugin manager.
 *
 * @see \Drupal\Core\Archiver\Annotation\Archiver
 * @see \Drupal\Core\Archiver\ArchiverInterface
 * @see plugin_api
 */
class ArchiverManager extends DefaultPluginManager {

  /**
   * Constructs a ArchiverManager object.
   *
   * @param \Traversable $namespaces
   *   An object that implements \Traversable which contains the root paths
   *   keyed by the corresponding namespace to look for plugin implementations.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   Cache backend instance to use.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler to invoke the alter hook with.
   */
  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
    parent::__construct(
      'Plugin/Archiver',
      $namespaces,
      $module_handler,
      'Drupal\Core\Archiver\ArchiverInterface',
      'Drupal\Core\Archiver\Annotation\Archiver'
    );
    $this->alterInfo('archiver_info');
    $this->setCacheBackend($cache_backend, 'archiver_info_plugins');
  }

}

This plugin type tells the system we'll be asking Drupal for any available Plugin/Archiver annotations and using that as our basic discovery methodology, support derivates, that other modules can alter the defined plugins using hook_archiver_info_alter() and the definitions are cached per language in the injected backend using the cache ID archiver_info_plugins.

Using the Plugin Manager

Assuming that our plugin type is all wired up, using it becomes fairly easy. First we must invoke the plugin class:

$type = \Drupal::service('plugin.manager.archiver');

Get a list of available plugins:

$plugin_definitions = $type->getDefinitions();

Get a specific plugin:

$plugin_definition = $type->getDefinition('plugin_id');

Create a preconfigured instance of a plugin:

$plugin = $type->createInstance('plugin_id', ['of' => 'configuration values']);

Discovery Decorators

Decorator classes are used to wrap the defined discovery class with another class(es) that implements all the same methods but provides some additional level of processing before or after that provided by the base discovery class. For example, the DefaultPluginManager, and thus any that extend it, allow for derivative discovery using the 

Example from DefaultPluginManager::getDiscovery():

  protected function getDiscovery() {
    if (!$this->discovery) {
      $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
      $this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
    }
    return $this->discovery;
  }

Find out more about this in Discovery Decorators.

Phase in your own Annotation type

When you initially created your custom module, you might have used Drupal's default @Plugin annotation type in your plugins, although this is not recommended. Now that you want to add additional keys, set defaults or simply document the annotation keys you are using, you discover you can't. You want to switch to your own annotation class @MyPlugin, but can't do so either, at least not immediately, to avoid breaking backwards-compatibility for custom plugins your users might have created.

Find out more about how to phase in your own Annotation type in Create your own custom annotation class.

Help improve this page

Page status: Needs work

You can: