Les formulaires
Formulaires de création et d'édition, DTOs, validation CSRF, MoneyType et messages flash.
Durant cet atelier, nous allons apprendre à créer et gérer des formulaires dans une application Symfony. Nous allons mettre en place des formulaires pour créer et modifier des entités telles que les portefeuilles (Wallets) et les dépenses (Expenses). Nous allons également voir comment valider les données saisies par les utilisateurs et comment gérer les erreurs de formulaire.
Pré-requis
Pour commencer cette partie, nous allons explorer les documentations suivantes :
- Symfony Forms
- La customisation du rendu d'un formulaire
- La validation des données dans les formulaires Symfony
- Implémentation de la protection CSRF
Nous allons également installer les packages suivants via Composer :
composer require symfony/form
composer require symfony/validator
composer require symfony/security-csrfCréation d'un nouveau wallet
Nous allons commencer par écrire le processus pour créer un nouveau wallet.
Pour ce faire, nous allons avoir besoin de plusieurs composants :
- Un nouveau controller
Wallets\Create(méthodeGETetPOST, route/wallets/create)- Génère le formulaire
- Le traite s'il a été posté
- Affiche le formulaire dans une vue Twig
- Un DTO
WalletDTOpour représenter les données du formulaire- Utilisé pour représenter les données du formulaire
- Contient les propriétés nécessaires pour créer un wallet (par exemple,
name)
- Un formulaire
WalletTypepour définir les champs du formulaire- Définit les champs du formulaire (par exemple,
name) - Ajoute des contraintes de validation (par exemple,
NotBlank,Length, etc.)
- Définit les champs du formulaire (par exemple,
- Un service
WalletServicepour gérer la logique de création du wallet- Contient tout le code métier pour créer un wallet (LLOC)
- Une vue Twig pour afficher le formulaire (normalement générée automatiquement par le
make:controller)- Affiche le formulaire
- Affiche les erreurs de validation si nécessaire
Consigne
Gestion de la DTO
- Créez un dossier
DTOdans le dossiersrc/Wallet. - Dans ce dossier, créez une classe
WalletDTOet ajoutez-y une propriété publiquename.
Gestion du formulaire
- Utilisez la commande
symfony console make:form WalletTypepour générer un formulaireWalletType. - Modifiez la classe
WalletTypepour ajouter un champnamede typeTextType, et un bouton de soumissionsubmit. - Ajoutez des contraintes de validation au champ
name:NotBlankpour s'assurer que le nom n'est pas vide.Lengthpour limiter la longueur du nom (par exemple, minimum 3 caractères, maximum 50 caractères).
- Configurez le formulaire pour qu'il utilise la classe
WalletDTOcomme data class. - Assurez-vous que le formulaire est protégé contre les attaques CSRF en activant l'option
csrf_protection.
Gestion du controller - affichage
- Créez le controller avec la commande
symfony console make:controller. - Modifiez le controller pour qu'il corresponde aux consignes ci-dessus.
- Dans la méthode du controller, créez une instance de
WalletDTO. - Créez le formulaire en utilisant la méthode
$this->createForm(WalletType::class, $walletDTO). - Passez le formulaire à la vue Twig en tant que paramètre.
Gestion de l'affichage - twig
- Dans la vue Twig générée, utilisez la fonction
form_start,form_widget, etform_endpour afficher le formulaire.
Gestion du controller - traitement
- Dans la méthode du controller, après la création du formulaire, ajoutez le code pour traiter la soumission du formulaire :
// traitement du formulaire par symfony, validations, etc.
$form->handleRequest($request);
// si le formulaire est soumis et valide
if ($form->isSubmitted() && $form->isValid()) {
// récupération des données du formulaire sous forme de la DTO WalletDTO
$dto = $form->getData();
$wallet = null;
try {
// traitements métier pour créer le wallet via le service WalletService
$wallet = $walletService->createWallet($dto, $this->getUser());
} catch (\Exception $e) {
// en cas d'erreur, ajout d'un message flash pour indiquer l'erreur
$this->addFlash('error', 'Erreur lors de la création du portefeuille');
// redirection vers la page de création du wallet
return $this->redirectToRoute('wallets_create');
}
// ajout d'un message flash pour indiquer le succès de l'opération
$this->addFlash('success', 'Portefeuille créé avec succès !');
// redirection vers le détail du wallet nouvellement créé
return $this->redirectToRoute('wallets_show', ['uid' => $wallet->getUid()]);
}- Injectez le service
WalletServicedans le controller via l'autowiring.
Gestion du code métier (Service)
- Créez un service
XUserWalletServicesi ce n'est pas déjà fait. - Dans ce service, créez une méthode
create(Wallet $wallet, User $user, string $role): XUserWalletpour gérer l'association entre un utilisateur et un wallet.- Vous devrez vérifier que l'utilisateur n'est pas déjà associé au wallet avant de créer une nouvelle association
- Si c'est le cas, remplacez le rôle de l'utilisateur dans le wallet.
- Si ce n'est pas le cas, créez une nouvelle entité
XUserWalletet persistez-la en base de données.
- Créez une méthode
createWallet(WalletDTO $dto, User $owner): Walletdans le serviceWalletServicepour gérer la création du wallet.- Créez une nouvelle entité
Walletet définissez son nom à partir de la DTO. - Persistez le wallet en base de données (+ flush).
- Utilisez le service
XUserWalletServicepour associer l'utilisateur propriétaire au wallet avec le rôleadmin. - Retournez l'entité
Walletcréée.
- Créez une nouvelle entité
Gestion des messages flash
- Dans le dossier
templates/_components, créez un fichier_flash_messages.html.twigsi ce n'est pas déjà fait. - Dans ce fichier, ajoutez le code pour afficher les messages flash (success, error, etc.) en utilisant les classes CSS appropriées.
- Incluez ce fichier dans vos deux layouts pour que les messages flash soient affichés sur toutes les pages.
Testez la création d'un nouveau wallet en accédant à la route /wallets/create, en remplissant le formulaire et en le soumettant.
Ajout du bouton "Créer un portefeuille" dans la navigation
- Sur la page de listing des wallets, ajoutez un bouton ou un lien "Créer un portefeuille" qui redirige vers la route
/wallets/create.- Utilisez la fonction
pathde Twig pour générer l'URL.
- Utilisez la fonction
- Stylisez le bouton ou le lien pour qu'il soit visible et attrayant.
Pour aller plus loin
Maintenant que vous savez comment créer un formulaire, nous allons utiliser le même procédé pour les autres formulaires nécessaires dans l'application.
- Création d'une DTO pour contenir les données envoyé par le formulaire.
- Création du formulaire avec les champs nécessaires et les contraintes de validation.
- Création du controller pour afficher et traiter le formulaire.
- Création du service pour gérer la logique métier.
- Création de la vue Twig pour afficher le formulaire.
Modification d'un wallet
La modification d'un wallet suit le même processus que la création, avec quelques différences :
- Le controller sera
Wallets\Editavec la route/wallets/{uid}/edit(méthodeGETetPOST). - Le formulaire
WalletTypesera le même que pour la création. - Le service
WalletServiceaura une méthodeupdateWallet(Wallet $wallet, WalletDTO $dto, User $updater): Walletpour gérer la mise à jour du wallet. - La vue Twig sera similaire à celle de la création, mais adaptée pour l'édition.
- Le formulaire sera pré-rempli avec les données existantes du wallet.
Astuce
Lors de la création, nous avons utilisé une DTO vide à passer au formulaire.
Comme vous le savez, la DTO contient les informations du formulaire. Lors de la modification, nous devons pré-remplir le formulaire avec les données existantes du wallet, et donc, pré-remplir la DTO avec ces données.
Pour cela, une étape nécessaire est d'ajouter une méthode statique fromEntity dans la DTO WalletDTO qui prendra en paramètre une entité Wallet et retournera une instance de WalletDTO remplie avec les données de l'entité.
Consigne
- Modifiez la DTO
WalletDTOpour ajouter un méthode statiquepublic static function fromEntity(Wallet $wallet): WalletDTOqui remplit une instance deWalletDTOavec les données de l'entitéWallet.
public static function fromEntity(Wallet $wallet): WalletDTO
{
// on crée une nouvelle instance de la DTO
$dto = new self();
// on remplit les propriétés de la DTO avec les données de l'entité
$dto->name = $wallet->getName();
return $dto;
}- Créez le controller
Wallets\EditControlleravec la route/wallets/{uid}/edit(méthodeGETetPOST). - Modifiez le controller pour :
- Que le wallet soit directement injecté dans la méthode via l'UID (utilisez le paramètre
Wallet $walletdans la méthode). - Créez une instance de
WalletDTOen utilisant la méthodefromEntitypour pré-remplir le formulaire. (ex :$walletDTO = WalletDTO::fromEntity($wallet);) - Le reste du traitement est similaire à celui de la création.
- Que le wallet soit directement injecté dans la méthode via l'UID (utilisez le paramètre
- Dans le service
WalletService, créez une méthodeupdateWallet(Wallet $wallet, WalletDTO $dto, User $updater): Walletpour gérer la mise à jour du wallet.- Mettez à jour les propriétés du wallet avec les données de la DTO.
- Persistez les modifications en base de données (+ flush).
- Retournez l'entité
Walletmise à jour. - Utilisez cette méthode dans le controller pour mettre à jour le wallet.
- Editez la vue Twig
edit.html.twigdans le dossiertemplates/wallets/editpour afficher le formulaire de modification. - Testez la modification d'un wallet en accédant à la route
/wallets/{uid}/edit, en modifiant les données du formulaire et en le soumettant. - Dans la vue Twig de détail du wallet (
details/index.html.twig), ajoutez un lien ou un bouton "Modifier le portefeuille" qui redirige vers la route/wallets/{uid}/edit.
Ajout d'un utilisateur à un wallet
Ce formulaire va être un peu différent, car il va demander de pré-remplir une liste déroulante avec les utilisateurs existants, moins ceux déjà associés au wallet.
Astuce
Dans un cas réel, on ferait en sorte d'ajouter l'utilisateur via son email, et on lui enverrai un mail d'invitation.
Cependant, pour simplifier l'exercice, nous allons simplement afficher une liste déroulante avec les utilisateurs non encore associés au wallet.
Pour ce faire, nous allons utiliser la capacité des formulaires symfony à recevoir des paramètres dans leur constructeur.
Astuce
Je vous conseille fortement de regarder la documentation sur l'ajout de paramètres à des formulaires : Passer des options à un formulaire.
Consigne
Gestion de la DTO
- Créez une DTO
XUserWalletDTOdans le dossiersrc/Wallet/DTOavec les propriétés nécessaires pour associer un utilisateur à un wallet (par exemple,userIdetrole).
Gestion du formulaire
- Créez un formulaire
XUserWalletTypeavec la commandesymfony console make:form. - Modifiez la classe
XUserWalletTypepour ajouter les champs nécessaires :- Un champ
userIdde typeChoiceType(rendu en select HTML) avec pouroptionsles utilisateurs non encore associés au wallet. - Un champ
rolede typeChoiceType(rendu en radio HTML) pour sélectionner le rôle de l'utilisateur dans le wallet (par exemple,admin,member).
- Un champ
- Pour remplir les options du champ
userId, modifiez la méthodeconfigureOptionspour accepter une optionavailable_usersqui sera un tableau d'utilisateurs. - Passez l'option
available_usersen tant que liste de choix dans le champuserId.
Gestion du controller
- Créez le controller
Wallets\AddUserControlleravec la route/wallets/{uid}/add-user(méthodeGETetPOST). - Modifiez le controller pour :
- Injecter le wallet via l'UID dans la méthode.
- Récupérer la liste des utilisateurs non encore associés au wallet (Vous devrez créer une méthode
findAvailableUsersForWallet(Wallet $wallet): arraydans leWalletServicepour ça). - Créez une instance de
XUserWalletDTO. - Créez le formulaire via
$this->createForm(...)en passant l'optionavailable_usersavec la liste des utilisateurs récupérés en troisième argument. - Traitez la soumission du formulaire de manière similaire aux autres formulaires.
Reste du traitement
- Utilisez la méthode
createdu serviceXUserWalletServicepour associer l'utilisateur au wallet avec le rôle sélectionné. - Assurez-vous du bon rendu du formulaire dans la vue Twig associée.
- Testez l'ajout d'un utilisateur à un wallet en accédant à la route
/wallets/{uid}/add-user, en sélectionnant un utilisateur et un rôle, puis en soumettant le formulaire. - Dans la vue Twig de détail du wallet (
details/index.html.twig), ajoutez un lien ou un bouton "Ajouter un utilisateur" qui redirige vers la route/wallets/{uid}/add-user.
Création d'une dépense
La création d'une dépense suit le même processus que la création d'un wallet, avec quelques différences :
- Le controller sera
Expenses\Createavec la route/wallets/{uid}/expenses/create(méthodeGETetPOST). - Le formulaire
ExpenseTypesera spécifique aux dépenses. - Le service
ExpenseServiceaura une méthodecreateExpense(Wallet $wallet, ExpenseDTO $dto, User $creator): Expensepour gérer la création de la dépense. - La vue Twig sera spécifique à la création de dépenses.
Consigne
- Créez une DTO
ExpenseDTOdans le dossiersrc/DTOavec les propriétés nécessaires pour créer une dépense (par exemple,amount,description). - Créez un formulaire
ExpenseTypeavec la commandesymfony console make:form. - Modifiez la classe
ExpenseTypepour ajouter les champs nécessaires :- Un champ
amountde typeMoneyTypepour saisir le montant de la dépense. - Un champ
descriptionde typeTextareaTypepour saisir la description
- Un champ
- Ajoutez des contraintes de validation aux champs du formulaire (par exemple,
NotBlank,Positivepour le montant). - Créez le controller
Expenses\CreateControlleravec la route/wallets/{uid}/expenses/create(méthodeGETetPOST). - Modifiez le controller pour :
- Injecter le wallet via l'UID dans la méthode.
- Créez une instance de
ExpenseDTO. - Créez le formulaire via
$this->createForm(ExpenseType::class, $expenseDTO). - Traitez la soumission du formulaire de manière similaire aux autres formulaires.
- Créez une méthode
createExpense(Wallet $wallet, ExpenseDTO $dto, User $creator): Expensedans le serviceExpenseServicepour gérer la création de la dépense.- Créez une nouvelle entité
Expenseet définissez ses propriétés avec les données de la DTO. - Associez la dépense au wallet.
- Persistez la dépense en base de données (+ flush).
- Retournez l'entité
Expensecréée.
- Créez une nouvelle entité
- Assurez-vous du bon rendu du formulaire dans la vue Twig associée.
- Testez la création d'une dépense en accédant à la route
/wallets/{uid}/expenses/create, en remplissant le formulaire et en le soumettant. - Dans la vue Twig de détail du wallet, ajoutez un lien ou un bouton "Ajouter une dépense" qui redirige vers la route d'ajout d'expense.
Astuce
Pour le champ amount, vous pouvez configurer le MoneyType pour qu'il utilise l'euro (€) comme devise par défaut.
Vous devrez aussi vous assurer de transformer correctement la valeur saisie en centimes (entier) avant de la persister en base de données.
Enfin, dans l'affichage, vous devrez formater le montant pour l'afficher en euros avec deux décimales.
Suppression d'une dépense
Pour faire une suppression, nous n'avons pas besoin de formulaire. Nous allons simplement créer un controller qui va gérer la suppression.
Consigne
- Créez le controller
Expenses\DeleteControlleravec la route/wallets/{wallet:uid}/expenses/{expense:uid}/delete(méthodeGET). - Modifiez le controller pour :
- Injecter le wallet et la dépense via leurs UIDs dans la méthode.
- Utilisez le service
ExpenseServicepour supprimer la dépense. (Vous devrez créer une méthodedeleteExpense(Expense $expense): voiddans le service pour ça). - Ajoutez un message flash pour indiquer le succès de l'opération.
- Redirigez vers la page de détail du wallet après la suppression.
- Créez une méthode
deleteExpense(Expense $expense): voiddans le serviceExpenseServicepour gérer la suppression de la dépense.- Supprimez l'entité
Expensede la base de données (passez isDeleted à true mais n'utilisez pas remove). - Flushez les changements en base de données.
- Supprimez l'entité
- Dans la vue Twig de détail du wallet, ajoutez, pour chaque dépense, un bouton qui lancera la suppression de la dépense en redirigeant vers la route de suppression.
- Testez la suppression d'une dépense en cliquant sur le bouton de suppression dans la vue Twig de détail du wallet.
Astuce
Pour les plus dégourdis, vous pouvez ajouter un système de popup/volet de confirmation pour la suppression d'une dépense.
Suppression d'un wallet
La suppression d'un wallet suit le même processus que la suppression d'une dépense, avec quelques différences :
- Le controller sera
Wallets\DeleteControlleravec la route/wallets/{uid}/delete(méthodeGET). - Le service
WalletServiceaura une méthodedeleteWallet(Wallet $wallet): voidpour gérer la suppression du wallet.
Consigne
- Créez le controller
Wallets\DeleteControlleravec la route/wallets/{uid}/delete(méthodeGET). - Modifiez le controller pour :
- Injecter le wallet via l'UID dans la méthode.
- Utilisez le service
WalletServicepour supprimer le wallet. (Vous devrez créer une méthodedeleteWallet(Wallet $wallet): voiddans le service pour ça). - Ajoutez un message flash pour indiquer le succès de l'opération.
- Redirigez vers la page de listing des wallets après la suppression.
- Créez une méthode
deleteWallet(Wallet $wallet): voiddans le serviceWalletServicepour gérer la suppression du wallet.- Supprimez l'entité
Walletde la base de données (passez isDeleted à true mais n'utilisez pas remove). - Flushez les changements en base de données.
- Supprimez l'entité
- Dans la vue Twig de détail du wallet, ajoutez un bouton "Supprimer le portefeuille" qui redirige vers la route de suppression.
- Testez la suppression d'un wallet en cliquant sur le bouton de suppression dans la vue Twig de détail du wallet.
Re-rendre le formulaire d'inscription fonctionnel
Actuellement, le formulaire d'inscription ne gère pas le champ name que nous avons ajouté à l'entité User.
Consigne
- Modifiez le type de formulaire de l'inscription pour ajouter le champ
namede typeTextType. - Ajoutez des contraintes de validation au champ
name(par exemple,NotBlank,Length). - Modifiez le controller d'inscription pour récupérer la valeur du champ
nameet la définir sur l'entitéUseravant de la persister en base de données. - Modifiez la vue Twig du formulaire d'inscription pour afficher le champ
name. - Testez le formulaire d'inscription en vous inscrivant avec un nom et en vérifiant que le nom est bien enregistré en base de données.