CMS

Journey To Drupal 8 Plugin’s

Drupal 8 has a powerful feature called Plugin that helps creating multiple elements, such as blocks, field types, field formatters, and many more.

Plugins can be created as part of modules in Drupal 8 and they provide a swappable and specific functionality that provides pluggable and stand-alone components which can be re-used whenever required. This is pretty much similar to the earlier versions of Drupal where modules and blocks were used as reusable components and programmable entities.

In Drupal 8,  Plugin manager is responsible for managing the plugins and both the definition and instantiating the instances of plugins.

The plugin system provides means to create specialized objects in Drupal that do not require the robust features of the entity system.

A Plugin is a combination of following elements:

  • Plugin Manager
  • Interface
  • Annotation Class

 

Plugin Manager

Plugin managers are defined as a service and used for discovery of a plugin.

Interface

Initially, we need to implement an Interface by adding a custom class in defining all required methods to make it work.

Annotation Class

Annotations are used to give a definition to a Plugin. This is a combination of different parameters in a comment style, however, that is readable by plugin manager.

The example given below follows the creation of new plugin type called Calculator that calculates  Area Of Shapes (Rectangle, Triangle, and Square) and BMI.

The primary step is to create a service that acts as a plugin manager and for that create a new file in module’s src directory called CalculatorPluginManager.php.

<?php

namespace Drupal\calculator;

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

/**
 * PLugin Manager for Claculator Plugins
 */
class CalculatorPluginManager extends DefaultPluginManager {

  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend,
    ModuleHandlerInterface $module_handler) {
    // This tells the plugin manager to look for Calculator plugins in the
    // 'src/Plugin/Calculator' subdirectory of any enabled modules.
    $subdir = 'Plugin/Calculator';
    // 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, than Drupal will throw an error.
    $plugin_interface = 'Drupal\calculator\CalculatorInterface';
    // The name of the annotation class that contains the plugin definition.
    $plugin_definition_annotation_name = 'Drupal\calculator\Annotation\Calculator';

    parent::__construct($subdir, $namespaces, $module_handler, $plugin_interface,
      $plugin_definition_annotation_name);

    $this->alterInfo('calculator_info');
    $this->setCacheBackend($cache_backend, 'calculator_info');
  }
}

We need to override the \Drupal\Core\Plugin|DefaultPluginManager class constructor to define the module handler and cache backend.

We override the constructor so that we can specify a cache key. This allows
plugin definitions to be cached and cleared properly, otherwise our plugin manager
will continuously read the disk to find plugins.

The next step is to create a calculator.services.yml in module’s directory. This will initiate the plugin discovery to Drupal.

services:
  plugin.manager.calculator:
    class: Drupal\calculator\CalculatorPluginManager
    parent: default_plugin_manager

Next step is to define plugin interface and that will be used by the plugin to implement the essential methods for the working of the plugin. Create a CalculatorInterface.php file in module’s src directory to hold the interface.

<?php
namespace Drupal\calculator;

use Drupal\Core\Form\FormStateInterface;

/**
 * An interface for all Calculator type plugins.
 */
interface CalculatorInterface {

  public function calculatorForm(array $form, FormStateInterface $form_state);

  public function calculatorFormValidate(array &$form, FormStateInterface $form_state);

  public function calculatorFormSubmit(array &$form, FormStateInterface $form_state);

}

Create CalculatorBase.php in your module’s src directory. This class will implement our CalculatorInterface interface:

<?php
namespace Drupal\calculator;

use Drupal\Component\Plugin\PluginBase;
use Drupal\calculator\CalculatorInterface;
use \Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Base class for Calculator Plugin Type
 */
class CalculatorBase extends PluginBase implements CalculatorInterface, FormInterface {

  protected $form_id;

  /**
   * {@inheritdoc}
   */
  public function getFormId(){
    return $this->form_id;
  }

  /**
   * {@inheritdoc}
   */
  public function __construct(){
    $this->form_id =  'calculator_form_'.$this->pluginDefinition['id'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state){
    return $form + $this->calculatorForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state){
    $this->calculatorFormValidate($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state){
    $this->calculatorFormSubmit($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
   public function calculatorForm(array $form, FormStateInterface $form_state){
     return [];
   }

   /**
    * {@inheritdoc}
    */
   public function calculatorFormValidate(array &$form, FormStateInterface $form_state){}

   /**
    * {@inheritdoc}
    */
   public function calculatorFormSubmit(array &$form, FormStateInterface $form_state){}
}

Now add a controller to make the plugin visible on a page.

<?php
namespace Drupal\calculator\controller;

use Drupal;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\calculator\CalculatorPluginManager;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Url;

class CalculatorController extends ControllerBase {

  /*
   * The Calculator Plugin Manager
   * @var Drupal\calculator\CalculatorPluginManager
   */
  private $calculator_plugin_manager;

  /*
   * The Form builder
   * @var \Drupal\Core\Form\FormBuilder
   */
  private $form_builder;

  /**
   * Constructs the CalculatorController.
   *
   * @param Drupal\calculator\CalculatorPluginManager $calculator_plugin_manager
   *   The Calculator Plugin Manager
   */
  public function __construct(CalculatorPluginManager $calculator_plugin_manager, FormBuilder $form_builder){
    $this->calculator_plugin_manager = $calculator_plugin_manager;
    $this->form_builder = $form_builder;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container){
      return new static(
        $container->get('plugin.manager.calculator'),
        $container->get('form_builder')
      );
  }

  /**
   * Lists all calculator plugins
   */
  public function list2(){
    $items = [];
    $calculator_plugins = $this->calculator_plugin_manager->getDefinitions();
    if(!empty($calculator_plugins)){
      foreach($calculator_plugins as $key => $plugin){
        $items[] = Drupal::l($plugin['title'], Url::fromRoute('calculator.load',['id' => $key]));
      }
    }
    return [
      '#theme' => 'item_list',
      '#list_type' => 'ul',
      '#title' => 'Calculators',
      '#items' => $items,
    ];
  }

  /**
   * Loads a calculator plugin
   *
   * @param string $id is the id of the plugin
   */
  public function load($id){
    $content = [];
    // Create a class instance through the manager.
    $plugin_instance = $this->calculator_plugin_manager->createInstance($id);
    // Return the calculator form
    $content['form'] = $this->form_builder->getForm($plugin_instance);
    return $content;
  }
}

The plugin has been created under the src directory of the module.

<?php
/**
 * @file
 * Contains \Drupal\calculator\Plugin\AreaCalculator.
 */

 namespace Drupal\calculator\Plugin\Calculator;

 use Drupal\calculator\CalculatorBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\OpenModalDialogCommand;


/**
 * Demonstrates various area calculators
 *
 * @Calculator(
 *   id = "bmi_calculator",
 *   title = "Bmi Calculator",
 *   description = @Translation("Bmi Calculator"),
 * )
 */
class BodyMassIndexCalculator extends CalculatorBase {

  public function calculatorForm(array $form, FormStateInterface $form_state){

    $form['bmi'] = [
      '#type' => 'details',
      "#title" => "BMI",
      '#open' => TRUE
    ];

    $form['bmi']['height'] = [
      '#type' => 'number',
      '#min'=> 1,
      '#step' => 'any',
      '#title' => t('Height in meters'),
    ];

    $form['bmi']['weight'] = [
      '#type' => 'number',
      '#min'=> 1,
      '#title' => t('Weight in kgs'),
    ];

    $form['bmi']['actions'] = array('#type' => 'actions');
    $form['bmi']['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => 'Calculate',
      '#name' => 'cal_bmi',
    ];

    return $form;
  }

  public function calculatorFormValidate(array &$form, FormStateInterface $form_state){}

  public function calculatorFormSubmit(array &$form, FormStateInterface $form_state){
    $values_submitted  = $form_state->getValues();
    if(isset($values_submitted['cal_bmi'])){
      $area = $values_submitted['weight'] / $values_submitted['height'];
      $area = $area/$values_submitted['height'];
      drupal_set_message(t('Body Mass Index  is : @area', ['@area' =>  $area]), 'status', FALSE);
    }
  }
}

Annotation directory is also under src directory and one more file called Calculator.php, see the example below.

<?php
namespace Drupal\calculator\Annotation;

use Drupal\Component\Annotation\Plugin;

/**
 * Defines a calculator annotation object.
 *
 * @Annotation
 */
class Calculator extends Plugin {
  /**
   * The plugin ID.
   *
   * @var string
   */
  public $id;

  /**
   * The plugin title.
   *
   *  @var \Drupal\Core\Annotation\Translation
   *
   *  @ingroup plugin_translatable
   */
  public $title;

  /**
   * A brief, human readable, description of the calculator type.
   *
   * This property is designated as being translatable because it will appear
   * in the user interface. This provides a hint to other developers that they
   * should use the Translation() construct in their annotation when declaring
   * this property.
   *
   * @var \Drupal\Core\Annotation\Translation
   *
   * @ingroup plugin_translatable
   */
  public $description;
}

Conclusion

By using the understanding of a plugin’s architecture in Drupal 8, multiple types of plugins can be created to resolve a different kind of problems.

Reference of the code for the plugin created above can be taken from the GitHub. Please see the link below.

https://github.com/vaishnavijupdi/drupal-plugin-manager

About The Author

Leave a Reply

*