Creating your own Plugin Manager

Last updated on
18 October 2016

This documentation is incomplete. Add more information.

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.

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\Component\Plugin\Factory\DefaultFactory;
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->factory = new DefaultFactory($this->getDiscovery());
  }

}

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

Note that DefaultFactory class expects the first parameter to be a DiscoveryInterface implementation, but we've passed it a PluginManagerBase-extending class. This is the right thing to do in most use cases for the following reasons:

  • The PluginManagerInterface (which the PluginManagerBase class implements) actually extends DiscoveryInterface, FactoryInterface and MapperInterface so it will work for any type-hinted parameters that expect any of these interfaces.
  • The plugin type actually proxies the methods defined by all of these interfaces and adds some additional logic on them for the more robust use cases.

Each factory can define its own constructor as necessary since FactoryInterface does not define a __construct() method. For developers writing custom Factories this can be very helpful.

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;
  }

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']);