A la découverte des plugins de Drupal 8 : les formateurs de champ

Champs avec montages en fond

Dans un précédent billet, nous avions découvert comment créer notre propre formateur de champ sur Drupal 7. Découvrons comment effectuer la même opération sur Drupal 8, avec son puissant système de Plugins.

Sans vouloir rentrer dans une description complète de tout le système, et pour faire très court, les plugins de Drupal 8 sont une approche orientée objet des différents hook_xxx_info() de Drupal 7 et de leurs hook associés (form, view, etc). Ils permettent d'implémenter certaines fonctionnalités tout en proposant une interface de configuration aux utilisateurs. Le système de block de Drupal 8 en est une parfaite illustration. Tout comme les styles de Views, les champs, leurs widgets, etc. Et comme beaucoup d'éléments de Drupal 8. Les plugins sont omniprésents.

Pour approfondir le sujet dès maintenant, vous pouvez vous référer par exemple à cet excellent billet Unraveling the Drupal 8 Plugin System ou encore à la documentation sur la plugin API.

Ainsi, sous Drupal 7 nous devions implémenter 5 hooks pour créer un formateur de champ :

  • hook_field_formatter_info(), qui va nous permettre de déclarer notre formateur à Drupal, ainsi que de définir toutes les options de paramétrages qui seront disponibles
  • hook_field_formatter_settings_form(), qui nous permettra de construire le formulaire de saisie des options de paramétrages
  • hook_field_formatter_settings_summary(), chargé d'afficher le récapitulatif des options de paramétrages sélectionnés dans le backoffice
  • hook_field_formatter_prepare_view(), pour préparer le rendu du champ, et permettre à d'autres modules de l'altérer si nécessaire
  • hook_field_formatter_view()

Désormais, avec Drupal 8, il nous suffit désormais d'implémenter un Plugin de type FieldFormatter qui contiendra l'ensemble des méthodes nécessaires au paramétrage et au rendu de notre champ.

Un bon dessin vaut mieux qu'un long discours. Prenons un exemple pour illustrer le propos. Nous allons créer un formateur de champ, qui s'appliquera aux champs de type texte et qui nous permettra de rendre le texte saisi sous la forme d'une icône, grâce à une police d'icône présente sur notre site.

Nous créons dans un premier temps notre module que nous allons intituler text_icon, et nous créons un simple fichier text_icon.info.yml avec les quelques informations indispensables pour faire connaître notre module à Drupal (name, type, description, core). 

# File text_icon.info.yml
name: Text Icon
type: module
description: 'Provides a field formatter to output an icon from a text field.'
core: 8.x

La magie des Plugins

Pour que notre plugin soit découvert automatiquement, il nous suffit de le placer sous l'arborescence suivante (merci PSR-4) : 

src/Plugin/Field/FieldFormatter

Dans lequel nous crééons le fichier TextIconFormatter.php qui va contenir notre classe PHP. Le chemin complet de notre formateur, depuis la racine de notre module, est alors :

src/Plugin/Field/FieldFormatter/TextIconFormatter.php

Le contenu de notre fichier pourrait être, dans une version très simplifée mais néanmoins suffisante.

/**
 * @file
 * Contains \Drupal\text_icon\Plugin\Field\FieldFormatter\TextIconFormatter.
 */

namespace Drupal\text_icon\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\StringFormatter;

/**
 * Plugin implementation of the 'text_icon' formatter.
 *
 * @FieldFormatter(
 *   id = "text_icon",
 *   label = @Translation("Text icon"),
 *   field_types = {
 *     "string",
 *   },
 *   quickedit = {
 *     "editor" = "plain_text"
 *   }
 * )
 */
class TextIconFormatter extends StringFormatter {

  /**
   * {@inheritdoc}
   */
  protected function viewValue(FieldItemInterface $item) {
    return [
      '#type' => 'inline_template',
      '#template' => '<i class="{{ value }}"></i>',
      '#context' => ['value' => $item->value],
    ];
  }

}

Que faisons-nous ici ?

Nous déclarons le namespace de notre formateur de champ.

Nous utilisons le système des annotations (@Fieldformatter) pour déclarer notre plugin, lui donner un identifiant (id), déclarer le type de champs pour lesquels ce formateur sera disponible et nous indiquons au module quickedit le type de champ pour permettre une modification directe.

Nous déclarons ensuite notre class TextIconFormatter qui étend la classe existante StringFormatter. Nous aurions pu étendre le plugin de base, qui fournit déjà le plus gros du travail, à savoir FormatterBase, mais comme nous ciblons spécifiquement des champs de type texte nous pouvons étendre la classe correspondante pour bénéficier déjà de toutes les implémentations sur ce type de champ. Pour un formateur spécifique à une image, nous aurions pu étendre la classe ImageFormatter.

Il nous reste alors à déclarer nos propres fonctions dans notre classe pour surcharger celles existantes dans les classes parentes. Dans notre exemple, nous ne modifions que la méthode viewValue nous permettant de rendre individuellement chaque élement du champ (qui pourrait très bien être mutliple) au moyen d'un template simplifié de Twig.

Pour les fonctions non déclarées dans notre classe, ce sont alors celles de son parent qui seront utilisées. 

Formateur de champ texte

Ainsi, n'ayant pas déclaré la méthode settingsForm dans notre classe, celle de la classe StringFormatter a été utilisée, et nous disposons d'un formulaire de configuration permettant de créer un lien vers le contenu sur notre champ texte qui sera rendu sous forme d'icone.

Notre formulaire de saisie ressemblera ainsi à un simple champ texte.

champ de saisie texte

Et nous obtenons un rendu sous la forme de l'icône correspondante

icone rendue

Les méthodes de base des formateurs

Il existe pour les formateurs de champs des méthodes de base que nous pourrons utiliser partout, qui sont déclarées dans l'interface FormatterInterface.

Construit le formulaire de saisie des options de paramétrages

function settingsForm(array $form, FormStateInterface $form_state);

Affiche le récapitulatif des options de paramétrages sélectionnées dans le backoffice

function settingsSummary();

Prépare le rendu des éléments du champ, en nous permettant de les altérer, de les modifier ou les compléter avec des informations supplémentaires.

function prepareView(array $entities_items);

Affiche le champ via le système de template Twig

function view(FieldItemListInterface $items, $langcode = NULL);

Affiche les différents éléments du champ, sous la forme d'un tableau prêt à être rendu.

function viewElements(FieldItemListInterface $items, $langcode);

La fonction de ces méthodes est identique à celles équivalentes sur drupal 7, et il vous suffit de les déclarer dans votre classe pour fournir des options de configuration et de rendu spécifiques.

Les formateurs déjà disponibles, tels que StringFormater, ImageFormatter pour ne citer que ceux donner en exemple dans ce billet, déclarent des méthodes supplémentaires spécifiques au type de champ auxquels ils s'appliquent, et il serait dommage de ne pas les utiliser si besoin. Pourquoi réinventer la roue ? N'hésitez pas à explorer et parcourir ces différentes classes, et leurs parents.

Et à l'inverse, si vous avez besoin de surcharger ou annuler une option ou un rendu spécifique fourni par le coeur, le système de Plugins vous permet de le faire en toute simplicité et en tout honneur.

Par exemple, si vous ne disposez pas déjà d'une police d'icône, ce serait dommage de devoir en déclarer sur l'ensemble du site, et le pénaliser en termes de performance, alors qu'elle ne sera utilisée que pour le rendu de ce type de champ. Nous pouvons alors attacher cette police directement à ce champ. Ainsi elle ne sera chargée que si ce champ est présent sur la page. Pour ce faire, il nous suffit simplement de surcharger la méthode view et d'attacher la librairie (cf. La gestion des librairies avec Drupal 8) à notre champ.

/**
 * {@inheritdoc}
 */
public function view(FieldItemListInterface $items, $langcode = NULL) {
  $elements = parent::view($items, $langcode);
  $elements['#attached']['library'][] = 'text_icon/fontawesome';
  return $elements;
}

Puisque nous surchargeons la méthode de la classe parente, c'est désormais notre propre classe qui est responsable de rendre les éléments. Pour cette raison, nous appelons la méthode de la classe parente, afin de ne pas dupliquer du code inutilement, puis nous pouvons attacher simplement notre librairie que nous aurons déclaré dans notre module au moyen du fichier text_icon.libraries.yml. Nous n'oublierons pas de déclarer la nouvelle interface FieldItemListInterface nécessaire à cette méthode dans notre fichier.

use Drupal\Core\Field\FieldItemListInterface;

Les plugins : un système puissant alliant simplicité et efficacité  

Cette nouvelle approche orientée objet, basée sur les Plugins, présente un avantage immédiatement préhensible. La lisibilité du code, où celui-ci, depuis la déclaration via les annotations jusqu'aux méthodes précisant le comportement du plugin, est disponible dans un seul et unique fichier. Et qui dit meilleur lisibilité dit une maintenance facilitée.

Autre avantage non négligeable : la quantité de code nécessaire est considérablement réduite, par l'utilisation des formateurs de base proposés par Drupal qui fournissent déjà le plus gros du travail : il ne reste plus qu'à personnaliser juste ce qui est nécessaire.

Cette très courte introduction aux Plugins de Drupal 8, via le prisme des formateurs de champ, ne fait qu'effleurer toute la puissance des plugins. Vous pouvez bien sûr utiliser, étendre, surcharger les plugins fournis par le coeur, mais vous pouvez aussi déclarer et construire vos propres types de Plugin.

Mais ceci est une autre histoire, que nous aurons très certainement l'occasion d'aborder prochainement.

 

Ajouter un commentaire