Les formulaires Symfony permettent de soumettre des données, de les valider, et pourquoi pas de transformer la valeur en une classe.
On peut aussi y associer une classe et laisser le formulaire mettre à jour les propriétés de celle-ci sans notre intervention.
Est-ce une bonne chose ? Pourquoi ne pas laisser nos formulaires faire le moins de choses possible ? Laissez moi vous expliquer en commençant par revoir quelques bases de séparation des préoccupations (separation of concerns).
En voiture
Vous avez une entité Voiture -> Marque, Moteur, Couleur, nombre de portes, climatisation, transmission, année.
On vous demande de créer en base de données une Voiture avec tous ces champs obligatoires.
Votre premier réflexe est d’utiliser peut-être symfony maker, make:entity
.
Le code qui va générer les getters et setters pour chaque propriétés.
Avant de parler du formulaire, qu’est-ce qui devrait vous choquer déjà ?
Moi c’est le fait qu’on nous impose du code, alors qu’on a rien demandé.
Comme si écrire car->setMotor
avait du sens ? ou car->setNbDoors
?
Reprenons : au tout début on nous oblige à créer une voiture avec tous les champs obligatoires, mais on ne nous a pas demandé de pouvoir changer le moteur ou le nombre de portes.
Posons la question à l’intéressé:
Développeur : Euh…on peut changer le moteur d’une voiture et son nombre de portes n’importe quand ?
Intéressé : Pardon ? je ne comprends pas
Développeur : Voilà dans mon code j’ai rajouté des setters pour pouvoir changer le moteur n’importe quand, ça a du sens pour vous ?
Intéressé : Je ne pense pas. D’ailleurs que sont ces fameux sept heures ?
Développeur : C’est pour changer la valeur d’une propriété d’une classe. Par exemple dans la classe Voiture, on pourrait changer n’importe quand le moteur par un autre.
Intéressé : On ne change pas le moteur d’une voiture n’importe quand, c’est étrange de faire ça non ?
Développeur : Oui vous avez raison…C’est parce que j’ai utilisé un outil qui m’a généré du code automatiquement et…. “Nom de Zeus !”
Du code généré automatiquement vous a conduit à poser une série de questions pertinentes, néanmoins était-il pertinent de laisser ce code généré automatiquement ?
Sûrement pas, c’est pour cela que quand j’utilise le maker de symfony pour ajouter une propriété dans mon entité, parce que c’est pratique, je vais supprimer les setters directement, même ceux inclus dans les méthodes Add*.
Revenons à notre code pour créer une voiture.
Est-ce que ceci vous semble intuitif :
Ou bien le code suivant :
Ce qui nous donnerait à l’utilisation :
La seconde méthode nous permets d’avoir un meilleur contrôle sur les paramètres qui vont être assignés à l’entité. On pourrait aller plus loin et mettre des Assertions pour s’assurer qu’on a bien le type attendu.
Mais vous allez vous dire, pourquoi j’ai besoin de faire ça, c’est mon code, je le connais, je connais l’ordre des paramètres !? Il y a une différence entre connaître les paramètres et ne pas avoir besoin de les connaître.
Ceux qui utilisent Doctrine et les Embeddable pourraient être tentés d’en créer, mais finalement ce qui importe c’est de sécuriser votre entité, non de rajouter des classes.
Et le rapport avec les formulaires ?
Symfony a un composant génial, le FormComponent. Hélas il a souvent été associé à des entités doctrine pour plus de simplicité et de rapidité.
Et pour qu’entité et form fonctionnent, il faut des getters/setters (pas que) sur chaque propriété de votre entité. De plus, quel est le but d’un formulaire ? Afficher une balise form dans une page html et récupérer les valeurs. Le lien entité/formulaire qui s’est installé dans les pratiques des développeurs est pour moi une très mauvaise habitude.
De un, on nous force à ajouter du code dans notre entité. De deux on peut mettre à jour une entité directement via un formulaire, sans intermédiaire. Et pour finir on va sûrement polluer notre entité avec des groupes de Validation (NotBlank, NotNull, Email etc).
Mais le pire c’est qu’on va polluer le code de notre projet pour l’adapter à un framework.
Symfony est un framework, une boîte à outils. Le FormComponent est capable de faire le lien entre les propriétés d’une classe et les champs du formulaire via le PropertyAccessComponent, c’est très pratique. La documentation de Symfony vous montre comment faire, mais ils n’expliquent pas, et ça n’est pas leur rôle, si c’est une bonne pratique de mettre une entité directement sur un formulaire pour pouvoir enregistrer en base de données dans la foulée.
Votre projet devrait respecter le principe open/closed au niveau du framework utilisé !
Je m’explique. Vous avez sûrement entendu parler d’architecture hexagonale, de clean architecture, de code SOLID ?
https://blog.octo.com/architecture-hexagonale-trois-principes-et-un-exemple-dimplementation/
Le principe repose sur le fait d’isoler votre code de l’extérieur, si vous avez besoin de faire appel à des bases de données, des requêtes http, des traductions, des validations, des contrôleurs etc…Des outils nécessaires mais externes à votre code, on va utiliser des interfaces pour communiquer avec l’extérieur.
Ainsi avec le composant Messenger de symfony, au lieu d’injecter directement leur interface MessageBusInterface, préférez plutôt votre interface. Vous pouvez alors choisir le Messenger de symfony et votre code devient moins couplé à une librairie externe, et vos tests vont vous dire merci !
Bon et bien pour les formulaires c’est pareil.
Faites des formulaires qui ne soient jamais reliés à une classe ou une entité.
Le seul intérêt d’un formulaire, c’est de récupérer les valeurs saisies et d’en faire ce qu’on veut ensuite. Pourquoi aurait-on besoin d’y associer une classe ?
En adoptant cette méthodologie, vous évitez de mettre à jour directement une entité ou de créer une classe “model” pour votre formulaire qui a peu d’intérêt.
Parenthèse sur les contraintes de validation
Si on a plus de classe model, ni d’entité, les contraintes de validation doivent bien aller quelque part. Et bien elles sont directement ajoutées dans votre formulaire. J’entends déjà des protestations au fond : Pourquoi découpler son formulaire si c’est pour lui ajouter des validations ?
Reprenons, un formulaire c’est fait pour soumettre des valeurs. Mais il faut bien s’assurer que les valeurs soumises correspondent à la valeur attendue. On ne parle pas de logique métier très poussée comme une validation sur un email unique en base de données. Non, ici on veut juste s’assurer que le champ email est bien un email, que le champ année est bien sélectionné, qu’une valeur est inférieure ou supérieure à une limite donnée etc.
Le formulaire et les validations de base vont de paire. Si vous avez besoin de vous assurer qu’un email n’est pas déjà utilisé en base de données, ça n’a rien à faire dans un formulaire, c’est une information qui provient de votre base de données, une api, un fichier excel….et donc une validation qui doit intervenir après avoir appelé $form->isValid()
.
Fin de parenthèse
Les données, valides et récupérées, on va les passer à un certain type de classe, un handler
.
Un handler est une classe qui va effectuer une et une seule chose. On peut être tenté de faire un super manager
comme en voit dans les anciennes écritures, avec des milliers de lignes de codes que personne ne peut déchiffrer. En créant une classe par action, vous allez abaisser la complexité de votre code, il sera plus simple à maintenir et à lire.
Symfony propose le composant Messenger, très pratique surtout pour faire de l’asynchrone. Il propose aussi un middleware avec doctrine pour effectuer un flush
à la fin d’un handler
, ça vous évite de devoir injecter entityManager
dans votre handler
.
Il y aussi SimpleBus, que personnellement je préfère utiliser et qui propose un MessageBus et un EventBus.
En fait j’utilise souvent celui de Symfony pour les messages asynchrones et le reste avec SimpleBus. C’est une question de goût, à tester.
Petite précision, au moment de l’écriture de cet article, j’ai eu l’information comme quoi SimpleBus ne serait plus maintenu sur les nouvelles versions de Symfony et les mainteneurs encouragent à utiliser le Messenger de Symfony. Donc n’utilisez pas le SimpleBus.
Conclusion
On l’a vu, les formulaires de Symfony peuvent vite devenir complexes alors que leur but principal est de récupérer les valeurs soumises par un utilisateur.
Les valeurs qu’on récupère peuvent alors être manipulées, analysées, sauvegardées par une classe dédiée.
On privilégie ainsi une bonne séparation des préoccupations, chaque étape est ainsi isolée des autres.
J’espère que cet article vous fera réfléchir à certains points et n’hésitez pas à donner votre avis !