Hello à tous ! Si vous suivez un peu mes articles sur ce blog (si vous ne le faites pas, honte à vous !), vous connaissez tous mon amour pour Drupal 7.
Mais avec la sortie de Drupal 8, je peux enfin vous proposez mon premier tuto, qui va regrouper un petit peu de tout : des custom Form, en passant par des custom Service et un peu d’API… On va se régaler !
Enjoy !
Mon premier custom module Drupal 8
Tout d’abord, je vais vous présenter le contexte et le but de ce module.
Nous allons créer un module nous permettant, depuis l’admin, d’uploader un fichier CSV contenant des informations regroupées par département. Nous allons sauvegarder ces infos en base, et mettre à disposition une API permettant d’appeler ces informations pour une appli externe.
Rien de bien compliqué, mais ça nous permettra d’avoir une vue d’ensemble de pas mal de nouvelles fonctionnalités Drupal 8.
Moment émotion : on va créer ensemble notre premier module custom Drupal 8 !
A la racine de votre projet, on va créer un répertoire dans le dossier modules/custom. Pour le nom, on va l’appeler rnsa_ws (mon originalité me perdra un jour).
Dans le dossier rnsa_ws, on va créer les fichiers de base d’un module, à savoir :
- rnsa_ws.info.yml (la description de notre module)
- rnsa_ws.routing.yml (le fichier de déclaration de nos routes pour le formulaire et l’API)
- rnsa_ws.services.yml (la déclaration de nos custom services)
- rnsa_ws.install (la déclaration de notre table en base de données)
- rnsa_ws.module (notre fichier pour modifier des hooks existants, mais on en aura pas d’utilité dans ce tuto)
Regardons ensemble, dans l’ordre, le contenu de ces fichiers :
Rien de bien neuf, à part la syntaxe YAML (enfin !)… Pour que Drupal reconnaisse votre module, ce fichier est indispensable. Il vous faut juste lui préciser le nom du module, sa description et le package dans lequel vous voulez le « ranger ».
Grande nouveauté Drupal 8 : le fichier de routing ! On se débarrasse de notre vieux hook_menu() et on passe à un véritable fichier de configuration. Petite explication qui va bien :
- la première route correspond à notre custom Form (qu’on verra tout à l’heure). On définit dans « path » le chemin souhaité, et ensuite on lui passe le formulaire ainsi qu’un titre (ça fait toujours bien)
- la deuxième, elle, correspond à la route de l’API qui nous renverra les infos. Pareil, on lui donne un chemin, on lui précise sur quel controller et quelle action elle doit taper, la méthode HTTP requise, et avec la propriété « _access », on peut définir des règles de restrictions d’accès. Moi je suis un mec sympa, je le mets à la disposition de tout le monde !
Dans ce fichier, on va déclarer notre service custom, qui se chargera d’enregistrer en base les données à l’upload du formulaire, ainsi que le service qui récupérera les données à l’appel de l’API. Rien de bien compliqué non plus, on déclare la classe du service, et on lui donne un nom pour pouvoir l’injecter. E-E-E-E-EASY.
On finit par un grand classique, une chose qui n’a pas changé, un roc, que dis-je, une péninsule… Le fichier de déclaration de notre schéma de table. Rien n’a changé, donc je vous invite à lire la documentation Drupal 7/8 (oui je suis fainéant).
C’est bien beau, les fichiers de configuration, mais maintenant, on fait quoi ?
Je sais que vous êtes pressé et que si vous chercher une solution à votre problème, vous ne lirez certainement pas mon récit palpitant, mais là on va attaquer les choses sérieuses. Là, on blague plus. On fait du code, du vrai, du beau, du SF2 quoi.
Dans votre module, créez un répertoire « src » et ensuite, créez l’arborescence suivante (je suis vraiment sympa, je vous mets l’image) :
On va commencer par l’interface de notre service, qu’on injectera dans le controller et le formulaire. Voici l’interface en question :
L’interface présente deux fonctions, une mettant à jour la table de risque et une autre, qui sera utilisée par le controller appelé par l’API pour renvoyer les résultats. Mais regardons maintenant le service en question, là où se fera toute la logique :
Notre service, qui implémente donc notre interface, présente les deux fonctions citées plus haut. La première se charge de mettre à jour la table à la soumission du formulaire qu’on verra tout à l’heure. On crée une connexion à la base de données, on boucle sur notre tableau, et on utilise la fonction « merge » qui permet de soit créer une entrée, soit de la mettre à jour si elle existe déjà.
Rien de bien fou en soi !
La deuxième est encore plus simple, puisqu’elle se contente de chercher l’entrée en base selon le département envoyé par lors de l’appel à l’API.
On a vu notre service, nous allons donc nous intéresser maintenant à notre formulaire !
Les formulaires custom sous Drupal 8 doivent obligatoirement étendre la classe FormBase. Et nous, puisqu’on est un peu foufou et qu’on veut absolument profiter de l’injection de dépendance, on va injecter notre service vu plus haut. Pour ça, lorsque nous allons créer le formulaire, nous allons injecter via notre container notre service. C’est beau, c’est propre, ça donne presque envie de pleurer.
Ensuite, notre formulaire doit implémenter 4 fonctions :
- getFormId()
- buildForm()
- validateForm()
- submitForm()
La première permet de définir l’ID de notre formulaire, pour que d’autre module puisse l’utiliser afin de le modifier avec un hook (sur un hook_form_alter() par exemple)
Je pense qu’on peut pas faire plus simple, il suffit juste de retourner une chaîne de caractères 🙂 .. Ensuite, regardons comment ajouter des éléments à notre formulaire avec la fonction buildForm()
Ici, on se rapproche beaucoup de la création de formulaire sous Drupal 7. Nous allons ajouter un champ « File » pour uploader notre CSV, et un bouton « Submit » (sans ça, il serait un peu useless notre formulaire …). Passons à la fonction de validation :
Notre fonction va ici, dans un premier temps, nous assurer que le fichier porte bien une extension en « .csv » uniquement. Ensuite, nous allons le rajouter dans le storage de la classe FormStateInterface (le petit frère de $form_state de Drupal 7) et déplacer le fichier dans le répertoire public pour l’utiliser dans la fonction submitForm(), que voici :
Ici, toute la logique prend place. On récupère le fichier dans le storage de $from_state, on ouvre le fichier (avec un petit appel au service Drupal de gestion de fichier pour faire bien), on le parcourt et on ajoute les données dans un tableau.
Tableau que l’on envoie ensuite à notre service, pour qu’il mette à jour notre table.
On en voit le bout !
Promis, c’est bientôt fini ! Il ne nous reste qu’à voir le controller, qui se chargera de dispatch la requête vers notre service pour récupérer les données et les renvoyer au format JSON. Let’s go !
Pour la construction de notre controller, on se rapproche beaucoup de la création de notre formulaire, sauf qu’ici, on étend ControllerBase et on injecte aussi le service qu’on a vu plus haut. Rien de bien compliqué, voyons maintenant la fonction que nous avons définie dans notre fichier de routing vu plus haut :
Ici, nous utilisons l’objet Request de SF2, ainsi que l’objet JsonResponse (de SF2 également). Nous checkons si l’appel présente bien un paramètre « department », si le département est bien compris entre 1 et 95 (désolé pour nos amis des DOM-TOM :/) et si c’est le cas, on appelle notre service pour récupérer la donnée, si on l’a, on la renvoie, sinon on lui dit qu’on n’a rien trouvé.
Voilà voilà !
Petite conclusion qui va bien
Mon premier module Drupal 8 a été une grosse découverte. Toutes mes habitudes Drupal 7 ont été particulièrement chamboulées, mais j’ai pris un véritable plaisir à avoir un code qui respecte davantage les bonnes pratiques du moment.
A très bientôt pour de nouvelles aventures !
P.S : Voilà le lien Github de ce tutorial si vous voulez jouer un peu avec : https://github.com/guillaumedeplanque/rnsa-ws 🙂