Travaillons ensemble !

Services

Plugin Derivatives sur Drupal : C’est quoi ?

Les Plugin Derivatives est un nouveau concept qui a été introduit sur Drupal 8.0 pour permettre d’avoir des instances de plugins dynamiques. On les trouve sur plusieurs modules du core de Drupal donnant ainsi plus de flexibilité aux développeurs lors de la création de Plugins liés à une configuration.

Mais comment on gérait cela sur Drupal 7 ?

Pour mieux comprendre le concept de Plugin Derivative, il vaut mieux revenir un peu en arrière pour retrouver nos réflexes du PHP procédural avec Drupal 7.
Prenons donc l’exemple de création de blocs. Comme on le sait, sur Drupal 7 pour déclarer un nouveau bloc il faudra implémenter hook_block_info() sur le fichier.module et retourner ainsi la liste des blocs déclarés par ce module sous forme de tableau PHP.

function mymodule_block_info() {

  // This example comes from node.module.
  $blocks['syndicate'] = array(
    'info' => t('Syndicate'),
    'cache' => DRUPAL_NO_CACHE,
  );
  $blocks['recent'] = array(
    'info' => t('Recent content'),
  );
  return $blocks;
}

Dans certains cas ou devrait construire le tableau des blocs depuis des données persistées comme le cas du module Menu. Sur ce module nous devons créer un bloc pour chaque menu custom créé en Back-office, ce qui fait qu’il faudra d’abord récupérer la liste des menus depuis la table “menu_custom” en base de données et ensuite ajouter un élément dans le tableau PHP des blocs pour chaque menu. Voici l’implémentation du hook_block_info() sur le module Menu de Drupal 7 :

function menu_block_info() {
  $menus = menu_get_menus(FALSE);
  $blocks = array();
  foreach ($menus as $name => $title) {
    $blocks[$name]['info'] = check_plain($title);

    // Menu blocks can't be cached because each menu item can have
    // a custom access callback. menu.inc manages its own caching.
    $blocks[$name]['cache'] = DRUPAL_NO_CACHE;
  }
  return $blocks;
}

On remarque donc l’utilisation d’une boucle foreach pour insérer les blocs avec des clés uniques sur le tableau $blocks. C’est tout donc !


Une nouvelle ère commence..


Avec la sortie de Drupal 8.0 et la refonte de l’architecture des modules en optant pour le paradigme orienté-objet, la notion de Plugins a été adoptée sur plusieurs concepts en partant des blocs tout en passant par les styles d’images, formats de textes, …etc
En principe, un Plugin est une classe PHP qui représente une fonctionnalité réutilisable sur le système et qui fait partie d’un Plugin Type. Prenons cet exemple : 

<?php

namespace Drupal\image\Plugin\ImageEffect;

use Drupal\Core\Image\ImageInterface;
use Drupal\image\ConfigurableImageEffectBase;

/**
 * Resizes an image resource.
 *
 * @ImageEffect(
 *   id = "image_resize",
 *   label = @Translation("Resize"),
 *   description = @Translation("Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.")
 * )
 */
class ResizeImageEffect extends ConfigurableImageEffectBase {

  /**
   * {@inheritdoc}
   */
  public function applyEffect(ImageInterface $image) {
    if (!$image
      ->resize($this->configuration['width'], $this->configuration['height'])) {
      $this->logger->error(error message…);
      return FALSE;
    }
    return TRUE;
  }

....

Ceci illustre la création d’un effet de redimensionnement des images qui sera détecté automatiquement par le système de création des styles d’images grâce à l’utilisation de l’annotation @ImageEffect. Il reste donc juste à définir les propriétés de ce plugin (id, label et description) et d’implémenter les méthodes de la classe ImageEffectInterface.

Si on revient au cas des blocs de menus, en suivant la même logique on devrait créer une classe PHP pour chaque menu avec l’annotation @Block, ce qui n’est pratiquement pas possible. C’est à ce moment là que l’utilisation de Plugin Derivative s’impose.

Un Plugin Derivative est tout simplement une classe qui implémente l’interface DeriverInterface et permet ainsi de retourner dynamiquement via la méthode getDerivativeDefinitions() la liste des définitions (instances) concernés.

En prenant toujours l’exemple des menus de blocs, voici à quoi correspond le Plugin Derivative associé : 

class SystemMenuBlock extends DeriverBase implements ContainerDeriverInterface {

  …
  /**
   * {@inheritdoc}
   */
  public function getDerivativeDefinitions($base_plugin_definition) {
    foreach ($this->menuStorage
      ->loadMultiple() as $menu => $entity) {
      $this->derivatives[$menu] = $base_plugin_definition;
      $this->derivatives[$menu]['admin_label'] = $entity
        ->label();
      $this->derivatives[$menu]['config_dependencies']['config'] = array(
        $entity
          ->getConfigDependencyName(),
      );
    }
    return $this->derivatives;
  }

}

Du coup il reste juste à créer un seul Plugin pour gérer tous les blocs de menus en spécifiant dans l’annotation le Derivative à utiliser :

/**
 * Provides a generic Menu block.
 *
 * @Block(
 *   id = "system_menu_block",
 *   admin_label = @Translation("Menu"),
 *   category = @Translation("Menus"),
 *   deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
 * )
 */
class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {

…
}

Conclusion

On voit clairement que les Plugin Derivatives permettent de créer facilement des Plugins dynamiques en partant de données persistées, tout en gardant une structure simple et élégante du code source.