We often use some prebuild structure as a starting point of our project/module and most of the Framework/CMS are now providing their CLI tools which automatically generate boilerplate templates by using some commands, Like ionic generate in ionic framework, php artisan create in Laravel and drupal generate in drupal console.

The basic idea behind those commands is to provide an interface, from which a developer can start writing solutions for a specific problem. Drupal plugin has a similar concept, the Base module defines plugin interface and some utility code. and other modules start by implementing that interface. However, we can define plugins in lots of other ways

  • It’s a new way in Drupal 8 to develop extensible modules.
  • It’s a general reusable solution for a recurring problem.
  • It provides a way to implement alterable functionality in modules.

Drupal documentation defines plugin as below.

The basic idea of plugins is to allow a particular module or subsystem of Drupal to provide functionality in an extensible, object-oriented way. The controlling module or subsystem defines the basic framework (interface) for the functionality, and other modules can create plugins (implementing the interface) with particular behaviors. The controlling module instantiates existing plugins as needed, and calls methods to invoke their functionality. Examples of functionality in Drupal Core that use plugins include: the block system (block types are plugins), the entity/field system (entity types, field types, field formatters, and field widgets are plugins), the image manipulation system (image effects and image toolkits are plugins), and the search system (search page types are plugins).

Plugins are grouped into plugin types, each generally defined by an interface. Each plugin type is managed by a plugin manager service, which uses a plugin discovery method to discover provided plugins of that type and instantiate them using a plugin factory.

Okay, let’s move forward and implement our custom plugin type.

We will do this in 3 part

  • Annotation — Define annotation for the plugin type
  • Plugin Manager — Define plugin manager which is responsible for discovering and loading plugins.
  • Plugin Interface and Base Class — Plugin signature and basic utility code.

Annotation —

Extend Plugin class to define our annotation, we will define keys here which will be used by the individual plugin.

File:- /modules/custom/kst/src/Annotation/KstPlugin.php

<?php

namespace Drupal\kst\Annotation;

use Drupal\Component\Annotation\Plugin;

/**
 * Defines a KstPlugin annotation object.
 *
 * Note that the "@ Annotation" line below is required and should be the last
 * line in the docblock. It's used for discovery of Annotation definitions.
 *
 * @see \Drupal\kst\KstPluginManager
 * @see plugin_api
 *
 * @Annotation
 */
class KstPlugin extends Plugin {

  /**
   * Description of plugin
   *
   * @var \Drupal\Core\Annotation\Translation
   *
   * @ingroup plugin_translatable
   */
  public $description;

  /**
   * Plugin settings
   *
   * @var $settings
   */
  public $settings = [];

}

Plugin Manager —

Define plugin manager, Plugin manager is responsible for plugin discovery and loading of plugin, It also defines hook through which other modules can alter plugin values during loading of the plugin.

File:- /modules/custom/kst/src/KstPluginManager.php

<?php

namespace Drupal\kst;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\kst\Annotation\KstPlugin;

/**
 * A plugin manager for kst plugins.
 *
 * The KstPluginManager class extends the DefaultPluginManager to provide
 * a way to manage kst plugins. A plugin manager defines a new plugin type
 * and how instances of any plugin of that type will be discovered, instantiated
 * and more.
 */
class KstPluginManager extends DefaultPluginManager {

  /**
   * Creates the discovery 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) {
    // We replace the $subdir parameter with our own value.
    // This tells the plugin manager to look for kst plugins in the
    // 'src/Plugin/Kst' subdirectory of any enabled modules. This also
    // serves to define the PSR-4 subnamespace in which kst plugins will
    // live. Modules can put a plugin class in their own namespace such as
    // Drupal\{module_name}\Plugin\Kst\MyKstPlugin.
    $subdir = 'Plugin/Kst';
    // The name of the interface that plugins should adhere to. Drupal will
    // enforce this as a requirement. If a plugin does not implement this
    // interface, Drupal will throw an error.
    $plugin_interface = KstPluginInterface::class;
    // The name of the annotation class that contains the plugin definition.
    $plugin_definition_annotation_name = KstPlugin::class;
    parent::__construct($subdir, $namespaces, $module_handler, $plugin_interface, $plugin_definition_annotation_name);
    // This allows the plugin definitions to be altered by an alter hook. The
    // parameter defines the name of the hook, thus: hook_kst_info_alter().
    $this->alterInfo('kst_info');
    // This sets the caching method for our plugin definitions.
    $this->setCacheBackend($cache_backend, 'kst_info');
  }

}

Plugin Interface and Base Class —

The plugin interface defines a signature for the plugin base class, and our plugin base class is an abstract class having some utility code and abstract functions.

File:- /modules/custom/kst/src/KstPluginInterface.php

<?php

namespace Drupal\kst;

/**
 * An interface for all Kst type plugins.
 *
 */
interface KstPluginInterface {

  /**
   * Provide a description of the kst plugin.
   *
   * @return string
   *   A string description of the kst plugin.
   */
  public function description();

  /**
   * Provide settings array
   *
   * @param null $setting_key
   *    A specific setting key
   *
   * @return mixed
   *    A settings field keys array
   */
  public function settings($setting_key = NULL);

}

File:- /modules/custom/kst/src/KstPluginBase.php

<?php

namespace Drupal\kst;

use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * A base class to help developers implement their own Kst plugins.
 *
 *
 * @see \Drupal\kst\Annotation\KstPlugin
 * @see \Drupal\kst\KstPluginInterface
 */
abstract class KstPluginBase extends PluginBase implements KstPluginInterface {

  /**
   * Data to be post
   *
   * @var $data
   */
  protected $data;

  /**
   * Settings keys array
   *
   * @var $apiKeys ;
   */
  protected $apiKeys;

  /**
   * {@inheritdoc}
   */
  public function description() {
    // Retrieve the @description property from the annotation and return it.
    return $this->pluginDefinition['description'];
  }

  /**
   * {@inheritdoc}
   */
  public function settings($setting_key = NULL) {
    if (!is_null($setting_key)) {
      return
        isset($this->pluginDefinition['settings'][$setting_key]) ?
          $this->pluginDefinition['settings'][$setting_key] :
          $this->pluginDefinition['settings'];
    }
    return $this->pluginDefinition['settings'];
  }

  /**
   * Provide form array
   *
   * @return array
   *    Setting form array
   */
  abstract public function formElements();

  /**
   * Validate input data
   *
   * @param array $form
   *    Form elements array
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *    Form state object
   *
   * @return mixed
   */
  abstract public function validateData(array &$form, FormStateInterface $form_state);

  /**
   * Execute plugin logic
   *
   * @param array $form
   *    Form elements array
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *    Form state object
   *
   * @return mixed
   */
  abstract public function executeLogic(array &$form, FormStateInterface $form_state);

}

Now let's create a demo plugin using our custom plugin type annotation.

Our Custom Plugin —

File:- /modules/custom/kst/src/Plugin/Kst/KstMetaTagGenerator.php

<?php

namespace Drupal\kst\Plugin\Kst;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\kst\KstPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;

/**
 * Provides a meta tag generator plugin.
 *
 * @KstPlugin(
 *   id = "kst_meta_tags",
 *   description = @Translation("Meta Tag Generator"),
 *   settings = {
 *    "ajax" = "true",
 *    "library" = "kst/metatag",
 *    "route" = "tools/metatag"
 *   }
 * )
 */
class KstMetaTagGenerator extends KstPluginBase implements ContainerFactoryPluginInterface {

  // Use Drupal\Core\StringTranslation\StringTranslationTrait to define
  // $this->t() for string translations in our plugin.
  use StringTranslationTrait;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    // This class needs to translate strings, so we need to inject the string
    // translation service from the container. This means our plugin class has
    // to implement ContainerFactoryPluginInterface. This requires that we make
    // this create() method, and use it to inject services from the container.
    // @see https://www.drupal.org/node/2012118
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('string_translation')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function formElements() {
    $form['title'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Site Title'),
      '#default_value' => '',
      '#size' => 60,
      '#maxlength' => 70,
      '#required' => TRUE,
    ];
    $form['description_keyword'] = [
      '#type' => 'container',
      '#prefix' => '<div class="row">',
      '#suffix' => '</div>',
    ];
    $form['description_keyword']['description'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Site Description'),
      '#prefix' => '<div class="col-md-6">',
      '#suffix' => '</div>',
    ];
    $form['description_keyword']['keywords'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Site Keywords'),
      '#description' => 'Separate with commas',
      '#prefix' => '<div class="col-md-6">',
      '#suffix' => '</div>',
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateData(array &$form, FormStateInterface $form_state) {
    // TODO: Implement validateData() method.
  }

  /**
   * {@inheritdoc}
   */
  public function executeLogic(array &$form, FormStateInterface $form_state) {
    // TODO: Implement executeLogic() method.
  }

}

Our custom plugin extends our KstPluginBase class and it must need to provide an implementation of abstract functions that are declared in the base class.

The above code is taken from a custom module that I have developed for a specific purpose. I hope all points are clear in the above article.