Rendre l'altération de formulaire Drupal 8 plus orientée objet grâce aux plugins

Rédigé par Sylvain Lavielle
Développeur web freelance expert Drupal sur Toulouse

Le 07/07/2019
keyboard_arrow_left Bonne lecture ? Que diriez-vous de partager. sentiment_satisfied_alt

Drupal 8 a initié des changements majeurs de sa structure en basant largement un grand nombre de ses fonctionnalités sur le framework Symfony. Le développement objet PHP dont la présence était relativement anecdotique dans la version de l'API de Drupal 7 est devenue prépondérante dans Drupal 8.

Cependant, cette mutation est encore incomplète et certaines parties de l'API Drupal font encore largement appel à une façon de coder très procédurale basée sur les hooks. Même si ce modèle de développement a contribué à faire de Drupal ce qu'il est, c'est clairement un mécanisme qui a fait son temps.

Il favorise l'apparition de gros ensembles de code relativement indigestes dans les fichiers .module. Même s'il est possible de répartir ce code dans d'autres fichiers, c'est un modèle de développement qui n'induit que peu de structuration et favorise la production de ce qu'on appelle du code spaghetti.

Personnellement, je trouve assez peu satisfaisant de devoir encore procéder de cette façon et j'ai souvent recours à quelques astuces pour adapter ces anciens mécanismes afin de les rendre plus compatibles avec le développement objet.

Voici par exemple comment on peut procéder dans le cas des hook_form_alter en utilisant l'une des possibilités offertes par Drupal 8: les plugins.

L'idée est donc de disposer d'un type de plugin custom (que nous allons appeler FormAlterer) qui permettra d'implémenter le même fonctionnement que les hook_form_alter

Création du type de plugin FormAlterer

La génération des fichiers de base d'un nouveau type de plugin utilisant les annotations peut être effectuée grâce à Drupal console :

drupal generate:plugin:type:annotation

Une fois les fichiers générés, il nous reste à leur apporter quelques modifications

Le plugin manager

Le plugin manager permet de gérer le type de plugin que nous allons implémenter. Nous allons ajouter la méthode dispatch qui nous servira plus tard pour appliquer l'altération de formulaire.

fichier : modules/custom/form_alterer/src/Plugin/FormAltererPluginManager.php

namespace Drupal\form_alterer\Plugin;
 
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
 
class FormAltererPluginManager extends DefaultPluginManager {
 
  public function __construct(
    \Traversable $namespaces,
    CacheBackendInterface $cache_backend,
    ModuleHandlerInterface $module_handler
  ) {
    parent::__construct(
      'Plugin/FormAltererPlugin',
      $namespaces,
      $module_handler,
      'Drupal\form_alterer\Plugin\FormAltererPluginInterface',
      'Drupal\form_alterer\Annotation\FormAltererPlugin'
    );
 
    $this->alterInfo('form_alterer_plugin_info');
    $this->setCacheBackend($cache_backend, 'form_alterer_plugin_plugins');
  }
 
  public function dispatch(&$form, FormStateInterface $form_state, $form_id){
 
    $definitions = $this->getDefinitions();
 
    foreach($definitions as $definition){
      if($form_id === $definition['formId']){
        $formAlterer = $this->createInstance($definition['id']);
        $formAlterer->alter($form, $form_state);
      }
    }
  }
}

L'interface du plugin

fichier : modules/custom/form_alterer/src/Plugin/FormAltererPluginInterface.php

namespace Drupal\form_alterer\Plugin;
 
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Form\FormStateInterface;
 
interface FormAltererPluginInterface extends PluginInspectionInterface {
 
  public function alter(&$form, FormStateInterface $form_state);
}

On ajoute ici la déclaration de la méthode alter qui sera nécessaire d'implémenter dans chaque plugin pour altérer le formulaire ciblé.

La classe de base du plugin

Il permet de définir le fonctionnement de base du plugin. Nous allons le laisser vide.

fichier : modules/custom/form_alterer/src/Plugin/FormAltererPluginBase.php

namespace Drupal\form_alterer\Plugin;
 
use Drupal\Component\Plugin\PluginBase;
 
abstract class FormAltererPluginBase 
  extends PluginBase implements FormAltererPluginInterface {
 
}

La classe d'annotation du plugin

C'est le fichier qui nous permet de définir les annotations.

fichier : modules/custom/form_alterer/src/Annotation/FormAltererPlugin.php

namespace Drupal\form_alterer\Annotation;
 
use Drupal\Component\Annotation\Plugin;
 
/**
 * Defines a Form alterer plugin item annotation object.
 *
 * @see \Drupal\form_alterer\Plugin\FormAltererPluginManager
 * @see plugin_api
 *
 * @Annotation
 */
class FormAltererPlugin extends Plugin {
 
  /**
   * The plugin ID.
   *
   * @var string
   */
  public $id;
 
  /**
   * The form ID.
   *
   * @var string
   */
  public $formId;
 
}

On ajoute à ce fichier la propriété formId qui permet de déclarer l'identifiant du formulaire à altérer par le plugin.

Cette propriété pourra donc être déclarée sous forme d'annotation dans le plugin.

Le hook_form_alter (le dernier)

Enfin nous allons déclarer un hook_form_alter dans notre fichier .module. Le dernier hook_form_alter dont nous aurons besoin. Il nous permettra d'intercepter les formulaires potentiels à altérer et de les dispatcher aux plugins.

fichier : modules/custom/form_alterer/form_alterer.module

use \Drupal\Core\Form\FormStateInterface;
 
function form_alterer_form_alter(&$form, FormStateInterface $form_state, $form_id) {
 
  /** @var Drupal\form_alterer\Plugin\FormAltererPluginManager $pluginManager */
  $pluginManager = \Drupal::service('plugin.manager.form_alterer_plugin');
  $pluginManager->dispatch($form, $form_state, $form_id);
}

Première altération avec un plugin FormAlterer

Nous sommes maintenant à pied d'oeuvre pour créer notre premier plugin FormAlterer.

Pour l'exemple nous allons nous contenter d'altérer le formulaire du bloc de newsletter

Fichier : modules/custom/my_custom_module/src/Plugin/FormAltererPlugin/NewsletterFormFormAlterer.php

namespace Drupal\my_custom_module\Plugin\FormAltererPlugin;
 
use Drupal\Console\Bootstrap\Drupal;
use Drupal\Core\Form\FormStateInterface;
use Drupal\form_alterer\Plugin\FormAltererPluginBase;
 
 
/**
 * newsletter_form Alteration
 *
 * @FormAltererPlugin(
 *   id = "my_custom_module_alter_newsletter_form",
 *   formId = "newsletter_form"
 * )
 */
class NewsletterFormFormAlterer extends FormAltererPluginBase {
  public function alter(&$form, FormStateInterface $form_state){
    $form['altered'] =  $form['altered'] = [
      '#weight' => 0,
      '#markup' => 'Ce formulaire à été altéré'
    ];
    $form['#submit'][] = self::class . '::submit';
  }
 
  static public function submit ($form, FormStateInterface $form_state){
    \Drupal::messenger()->addStatus('et la fonction submit à été altérée aussi');
  }
}

On obtient ainsi des plugins faciles à implémenter, qui prennent une place logique dans la topologie des namespaces de classes Drupal 8. Il sera donc facile d'identifier leur présence dans un module et d'en déduire leur fonction. Le code qu'ils contiennent n'est pas sensiblement différent de celui qu'on aurait implémenté pour un hook_form_alter classique, mais l'ensemble du code relatif à une altération de formulaire pourra être localisée dans une classe bien précise plutôt que d'être disséminée dans un fichier .module.

On y gagne donc en maintenabilité, en lisibilité et en confort.

Ce principe de plugin est assez facilement déclinable pour d'autres hooks.

Sujets abordés dans cet article