Adrien Gras
Ateliers

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 :

Nous allons également installer les packages suivants via Composer :

composer require symfony/form
composer require symfony/validator
composer require symfony/security-csrf

Cré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éthode GET et POST, route /wallets/create)
    • Génère le formulaire
    • Le traite s'il a été posté
    • Affiche le formulaire dans une vue Twig
  • Un DTO WalletDTO pour 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 WalletType pour 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.)
  • Un service WalletService pour 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

  1. Créez un dossier DTO dans le dossier src/Wallet.
  2. Dans ce dossier, créez une classe WalletDTO et ajoutez-y une propriété publique name.

Gestion du formulaire

  1. Utilisez la commande symfony console make:form WalletType pour générer un formulaire WalletType.
  2. Modifiez la classe WalletType pour ajouter un champ name de type TextType, et un bouton de soumission submit.
  3. Ajoutez des contraintes de validation au champ name :
    • NotBlank pour s'assurer que le nom n'est pas vide.
    • Length pour limiter la longueur du nom (par exemple, minimum 3 caractères, maximum 50 caractères).
  4. Configurez le formulaire pour qu'il utilise la classe WalletDTO comme data class.
  5. Assurez-vous que le formulaire est protégé contre les attaques CSRF en activant l'option csrf_protection.

Gestion du controller - affichage

  1. Créez le controller avec la commande symfony console make:controller.
  2. Modifiez le controller pour qu'il corresponde aux consignes ci-dessus.
  3. Dans la méthode du controller, créez une instance de WalletDTO.
  4. Créez le formulaire en utilisant la méthode $this->createForm(WalletType::class, $walletDTO).
  5. Passez le formulaire à la vue Twig en tant que paramètre.

Gestion de l'affichage - twig

  1. Dans la vue Twig générée, utilisez la fonction form_start, form_widget, et form_end pour afficher le formulaire.

Gestion du controller - traitement

  1. 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()]);
        }

  1. Injectez le service WalletService dans le controller via l'autowiring.

Gestion du code métier (Service)

  1. Créez un service XUserWalletService si ce n'est pas déjà fait.
  2. Dans ce service, créez une méthode create(Wallet $wallet, User $user, string $role): XUserWallet pour 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é XUserWallet et persistez-la en base de données.
  3. Créez une méthode createWallet(WalletDTO $dto, User $owner): Wallet dans le service WalletService pour gérer la création du wallet.
    • Créez une nouvelle entité Wallet et définissez son nom à partir de la DTO.
    • Persistez le wallet en base de données (+ flush).
    • Utilisez le service XUserWalletService pour associer l'utilisateur propriétaire au wallet avec le rôle admin.
    • Retournez l'entité Wallet créée.

Gestion des messages flash

  1. Dans le dossier templates/_components, créez un fichier _flash_messages.html.twig si ce n'est pas déjà fait.
  2. Dans ce fichier, ajoutez le code pour afficher les messages flash (success, error, etc.) en utilisant les classes CSS appropriées.
  3. 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

  1. 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 path de Twig pour générer l'URL.
  2. 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.

  1. Création d'une DTO pour contenir les données envoyé par le formulaire.
  2. Création du formulaire avec les champs nécessaires et les contraintes de validation.
  3. Création du controller pour afficher et traiter le formulaire.
  4. Création du service pour gérer la logique métier.
  5. 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\Edit avec la route /wallets/{uid}/edit (méthode GET et POST).
  • Le formulaire WalletType sera le même que pour la création.
  • Le service WalletService aura une méthode updateWallet(Wallet $wallet, WalletDTO $dto, User $updater): Wallet pour 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

  1. Modifiez la DTO WalletDTO pour ajouter un méthode statique public static function fromEntity(Wallet $wallet): WalletDTO qui remplit une instance de WalletDTO avec 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;
    }

  1. Créez le controller Wallets\EditController avec la route /wallets/{uid}/edit (méthode GET et POST).
  2. Modifiez le controller pour :
    • Que le wallet soit directement injecté dans la méthode via l'UID (utilisez le paramètre Wallet $wallet dans la méthode).
    • Créez une instance de WalletDTO en utilisant la méthode fromEntity pour pré-remplir le formulaire. (ex : $walletDTO = WalletDTO::fromEntity($wallet);)
    • Le reste du traitement est similaire à celui de la création.
  3. Dans le service WalletService, créez une méthode updateWallet(Wallet $wallet, WalletDTO $dto, User $updater): Wallet pour 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é Wallet mise à jour.
    • Utilisez cette méthode dans le controller pour mettre à jour le wallet.
  4. Editez la vue Twig edit.html.twig dans le dossier templates/wallets/edit pour afficher le formulaire de modification.
  5. 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.
  6. 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

  1. Créez une DTO XUserWalletDTO dans le dossier src/Wallet/DTO avec les propriétés nécessaires pour associer un utilisateur à un wallet (par exemple, userId et role).

Gestion du formulaire

  1. Créez un formulaire XUserWalletType avec la commande symfony console make:form.
  2. Modifiez la classe XUserWalletType pour ajouter les champs nécessaires :
    • Un champ userId de type ChoiceType (rendu en select HTML) avec pour options les utilisateurs non encore associés au wallet.
    • Un champ role de type ChoiceType (rendu en radio HTML) pour sélectionner le rôle de l'utilisateur dans le wallet (par exemple, admin, member).
  3. Pour remplir les options du champ userId, modifiez la méthode configureOptions pour accepter une option available_users qui sera un tableau d'utilisateurs.
  4. Passez l'option available_users en tant que liste de choix dans le champ userId.

Gestion du controller

  1. Créez le controller Wallets\AddUserController avec la route /wallets/{uid}/add-user (méthode GET et POST).
  2. 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): array dans le WalletService pour ça).
    • Créez une instance de XUserWalletDTO.
    • Créez le formulaire via $this->createForm(...) en passant l'option available_users avec 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

  1. Utilisez la méthode create du service XUserWalletService pour associer l'utilisateur au wallet avec le rôle sélectionné.
  2. Assurez-vous du bon rendu du formulaire dans la vue Twig associée.
  3. 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.
  4. 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\Create avec la route /wallets/{uid}/expenses/create (méthode GET et POST).
  • Le formulaire ExpenseType sera spécifique aux dépenses.
  • Le service ExpenseService aura une méthode createExpense(Wallet $wallet, ExpenseDTO $dto, User $creator): Expense pour gérer la création de la dépense.
  • La vue Twig sera spécifique à la création de dépenses.

Consigne

  1. Créez une DTO ExpenseDTO dans le dossier src/DTO avec les propriétés nécessaires pour créer une dépense (par exemple, amount, description).
  2. Créez un formulaire ExpenseType avec la commande symfony console make:form.
  3. Modifiez la classe ExpenseType pour ajouter les champs nécessaires :
    • Un champ amount de type MoneyType pour saisir le montant de la dépense.
    • Un champ description de type TextareaType pour saisir la description
  4. Ajoutez des contraintes de validation aux champs du formulaire (par exemple, NotBlank, Positive pour le montant).
  5. Créez le controller Expenses\CreateController avec la route /wallets/{uid}/expenses/create (méthode GET et POST).
  6. 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.
  7. Créez une méthode createExpense(Wallet $wallet, ExpenseDTO $dto, User $creator): Expense dans le service ExpenseService pour gérer la création de la dépense.
    • Créez une nouvelle entité Expense et 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é Expense créée.
  8. Assurez-vous du bon rendu du formulaire dans la vue Twig associée.
  9. 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.
  10. 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

  1. Créez le controller Expenses\DeleteController avec la route /wallets/{wallet:uid}/expenses/{expense:uid}/delete (méthode GET).
  2. Modifiez le controller pour :
    • Injecter le wallet et la dépense via leurs UIDs dans la méthode.
    • Utilisez le service ExpenseService pour supprimer la dépense. (Vous devrez créer une méthode deleteExpense(Expense $expense): void dans 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.
  3. Créez une méthode deleteExpense(Expense $expense): void dans le service ExpenseService pour gérer la suppression de la dépense.
    • Supprimez l'entité Expense de la base de données (passez isDeleted à true mais n'utilisez pas remove).
    • Flushez les changements en base de données.
  4. 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.
  5. 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\DeleteController avec la route /wallets/{uid}/delete (méthode GET).
  • Le service WalletService aura une méthode deleteWallet(Wallet $wallet): void pour gérer la suppression du wallet.

Consigne

  1. Créez le controller Wallets\DeleteController avec la route /wallets/{uid}/delete (méthode GET).
  2. Modifiez le controller pour :
    • Injecter le wallet via l'UID dans la méthode.
    • Utilisez le service WalletService pour supprimer le wallet. (Vous devrez créer une méthode deleteWallet(Wallet $wallet): void dans 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.
  3. Créez une méthode deleteWallet(Wallet $wallet): void dans le service WalletService pour gérer la suppression du wallet.
    • Supprimez l'entité Wallet de la base de données (passez isDeleted à true mais n'utilisez pas remove).
    • Flushez les changements en base de données.
  4. Dans la vue Twig de détail du wallet, ajoutez un bouton "Supprimer le portefeuille" qui redirige vers la route de suppression.
  5. 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

  1. Modifiez le type de formulaire de l'inscription pour ajouter le champ name de type TextType.
  2. Ajoutez des contraintes de validation au champ name (par exemple, NotBlank, Length).
  3. Modifiez le controller d'inscription pour récupérer la valeur du champ name et la définir sur l'entité User avant de la persister en base de données.
  4. Modifiez la vue Twig du formulaire d'inscription pour afficher le champ name.
  5. 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.

Sur cette page