Aller au contenu

Modifier et supprimer : en autonomie

Temps 1 · séance 3 · ~60 min + maison

Les slides du chapitre (PDF)

  • Écrire seul la page de modification d'un projet, à partir de la page de création.
  • Réutiliser la même Form Request pour créer et pour modifier.
  • Supprimer un projet après une confirmation dans une boîte de dialogue.
  • Trouver la cause d'un envoi qui échoue, dans l'onglet Réseau ou dans le journal de Laravel.

1. Ce que vous construisez

Votre back-office sait lister et créer. Il lui manque la modification et la suppression, les deux dernières lettres du CRUD de votre cahier des charges. Vous les écrivez seul, avec ce que les chapitres 1.10 à 1.12 vous ont montré. Ce chapitre ne donne pas le code : il donne ce qui doit marcher, où chercher, et les pièges.

Les critères

Côté serveur, dans Admin\ProjectController :

  • edit(Project $project): Response rend la page admin/projects/Edit avec deux props : le projet et la liste des étiquettes.
  • Le projet envoyé est un tableau construit, avec id, title, summary, content, demo_url, repo_url et les numéros de ses étiquettes. Jamais le modèle entier.
  • update(ProjectRequest $request, Project $project): RedirectResponse enregistre les champs validés, synchronise les étiquettes, prépare la notification « Projet modifié. » et redirige vers la liste.
  • destroy(Project $project): RedirectResponse supprime le projet, prépare la notification « Projet supprimé. » et redirige vers la liste.
  • ProjectRequest reste la seule Form Request du projet : les règles ne sont écrites qu'une fois.

Côté navigateur :

  • La page admin/projects/Edit.vue affiche le formulaire du chapitre 1.12, prérempli avec les valeurs du projet, cases à cocher comprises.
  • Le formulaire s'envoie avec <Form v-bind="ProjectController.update.form(project)">.
  • Les erreurs s'affichent sous les champs, comme à la création, et le bouton « Enregistrer » se désactive pendant l'envoi.
  • Dans la liste, chaque ligne a un bouton « Modifier », qui est un lien vers la page de modification, et un bouton « Supprimer ».
  • « Supprimer » ouvre une boîte de dialogue qui nomme le projet. La boîte contient un <Form v-bind="ProjectController.destroy.form(project)">, un bouton « Annuler » et un bouton de confirmation.
  • Après l'enregistrement ou la suppression, vous revenez à la liste avec une notification, et la boîte est fermée.

Vérifiez dans le navigateur

  • Le fil d'Ariane de la page de modification affiche « Projets › » suivi du titre du projet.
  • Un titre vidé affiche « Le champ titre est obligatoire. », et les autres champs gardent leur valeur.
  • Un titre modifié apparaît dans la liste et sur la fiche, mais l'adresse /projets/… du projet ne change pas.
  • Une étiquette décochée disparaît du projet, ce que vous vérifiez sur sa fiche publique ou avec Tinker.
  • « Annuler » ferme la boîte sans rien supprimer. La confirmation retire la ligne de la liste.

La page de modification du projet Quiz des capitales : fil d'Ariane Projets, Quiz des capitales ; titre, résumé et lien de la démo remplis ; les cases JavaScript et Vue.js cochées, Laravel décochée ; bouton Enregistrer

La liste des projets assombrie, avec au centre une boîte de dialogue : Supprimer « Carnet de recettes » ?, une phrase d'avertissement, les boutons Annuler et Supprimer

2. Ce que vous avez déjà

Presque tout existe déjà dans votre projet, ou dans le kit.

Pour Partez de
la page de modification votre Create.vue du chapitre 1.12 : même formulaire, autre fonction de Wayfinder, valeurs de départ
des champs préremplis la page de profil du kit, resources/js/pages/settings/Profile.vue, qui préremplit le nom et l'adresse e-mail
une suppression confirmée resources/js/components/DeleteUser.vue, la suppression du compte dans le kit : un Dialog qui contient un <Form>
la logique du serveur votre store() du chapitre 1.12, et le chapitre 16 de 5XCOS, « Formulaires : modifier et supprimer »
le bouton-lien « Modifier » le bouton « Nouveau projet » du chapitre 1.11, avec as-child

Les ressources

Ressource Ce que vous y cherchez
Inertia, page « Forms » la section « Form Component » : les « Slot Props » comme errors et processing, et les « Events » comme success
shadcn-vue, page « Dialog » les morceaux de la boîte (DialogTrigger, DialogContent, DialogClose…) et l'exemple « Dialog with form »
DeleteUser.vue du kit un Dialog, un <Form v-bind="….destroy.form()"> dedans, un bouton d'annulation dans DialogClose
5XCOS, chapitre 16 edit(), update(), destroy(), la Form Request partagée et le champ _method en Blade

Ouvrez aussi le fichier que Wayfinder a généré pour votre contrôleur, resources/js/actions/App/Http/Controllers/Admin/ProjectController.ts. Les fonctions update et destroy y sont depuis le chapitre 1.10 : Wayfinder suit les routes déclarées par Route::resource(), même avant que les méthodes existent dans le contrôleur.

3. Les pièges

Le champ _method

En 5XCOS, un formulaire de modification contenait @method('PUT'), qui écrivait un champ caché _method. Le navigateur n'envoie un formulaire qu'en GET ou en POST, et Laravel lit _method pour savoir quelle route chercher.

Avec Wayfinder, vous n'écrivez rien de tout cela. ProjectController.update.form(project) renvoie une adresse qui contient déjà la méthode, et la méthode post. Pour le projet numéro 2, le navigateur reçoit cette balise :

<form action="/admin/projets/2?_method=PUT" method="post">

Laravel lit _method dans l'adresse aussi bien que dans un champ caché. destroy.form(project) fait la même chose avec _method=DELETE. Si vous écrivez l'adresse à la main, avec method="post" et sans _method, la route POST admin/projets/{project} n'existe pas, et Laravel répond par le code 405, comme au chapitre 16 de 5XCOS.

ProjectController.update.form(project) écrit pour le projet numéro 2 la balise form avec l’adresse /admin/projets/2?_method=PUT et la méthode post ; le navigateur envoie un POST, Laravel lit _method et choisit la route PUT, donc update(), ou DELETE, donc destroy() ; sans _method, la route POST admin/projets/{project} répond 405

La route avec paramètre

update.form(project) construit l'adresse avec le numéro du projet. Wayfinder le cherche dans la clé id de l'objet reçu, comme le dit la signature générée :

update.url = (args: { project: number | { id: number } } | [project: number | { id: number } ] | number | { id: number }, options?: RouteQueryOptions) => {

Si le tableau envoyé par edit() n'a pas de clé id, la fonction ne trouve aucun numéro, et la page ne s'affiche pas. La console du navigateur montre alors une TypeError. Mettez id dans les props de edit(), même si la page ne l'affiche pas.

Les valeurs de départ

Un champ non contrôlé reçoit sa valeur de départ par la prop :default-value, comme dans la page de profil du kit. Pour les cases à cocher, la valeur de départ est true ou false : la case est cochée si le numéro de l'étiquette figure dans les étiquettes du projet.

<Checkbox
    name="tags[]"
    :value="String(tag.id)"
    :default-value="project.tags.includes(tag.id)"
/>

Côté serveur, $project->tags()->pluck('tags.id') donne la liste des numéros d'étiquettes du projet.

Le fil d'Ariane qui dépend d'une prop

Dans Create.vue, le fil d'Ariane est un objet fixe, écrit dans defineOptions. Dans Edit.vue, il contient le titre du projet, qui est une prop. Or defineOptions ne voit pas les props de la page. Inertia accepte une fonction à la place de l'objet : il l'appelle avec les props de la page, et elle renvoie les options du layout.

defineOptions({
    layout: (pageProps) => ({
        breadcrumbs: [
            { title: 'Projets', href: index() },
            { title: pageProps.project.title, href: edit(pageProps.project) },
        ],
    }),
});

Le Dialog qui ne se ferme pas

Placez la boîte de dialogue dans la ligne du tableau, une boîte par projet, comme dans DeleteUser.vue. Après la suppression, la ligne disparaît de la liste, et sa boîte disparaît avec elle.

Une seule boîte placée hors du tableau, ouverte par une variable, se comporte autrement. Après la suppression, la ligne disparaît et la notification s'affiche, mais la boîte reste ouverte avec « Supprimer « … » ? ». La redirection ramène sur la même page, Inertia garde le composant et son état, et la variable n'est jamais remise à zéro. Si vous choisissez cette version, remettez la variable à zéro dans l'événement @success du <Form>.

Le bouton « Annuler » se place dans un DialogClose as-child. Ce composant lui donne type="button" : un clic ferme la boîte sans envoyer le <Form> qui l'entoure.

À gauche, une boîte Dialog dans chaque ligne du tableau : après la suppression, la ligne disparaît et sa boîte avec elle. À droite, une seule boîte hors du tableau reste ouverte sur Supprimer « … » ? tant que la variable n’est pas remise à zéro dans @success ; en bas, le bouton Annuler dans DialogClose as-child reçoit type="button" et ferme sans envoyer le Form

Le slug ne change pas

La Form Request du chapitre 1.12 ne calcule le slug qu'à la création, quand la requête est un POST. À la modification, elle ne le touche pas, et sa règle prohibited refuse même un slug qu'une requête fabriquée enverrait. Un projet garde donc son adresse publique toute sa vie : si vous corrigez le titre du quiz, la liste et la fiche affichent le nouveau titre, et /projets/quiz-des-capitales répond toujours. Un lien partagé sur LinkedIn ne casse pas.

4. Quand ça casse

Quatre pannes, du symptôme à l’endroit où regarder : rien ne s’affiche, une redirection 303, la prop errors dans l’onglet Réseau, un champ sans InputError ; une page d’erreur, une erreur 500, aussi dans storage/logs/laravel.log ; une erreur 404, un numéro absent de la base ; une page blanche, une TypeError dans la console, un projet sans id

Le formulaire ne s'enregistre pas, et rien ne s'affiche

Un envoi refusé répond par une redirection, 303 ici parce que la méthode est PUT, puis la page reçoit les messages dans errors, comme au chapitre 1.12. Si aucun message n'apparaît, l'erreur porte sur un champ qui n'a pas d'InputError sous lui. Ouvrez l'onglet Réseau, cliquez sur la dernière requête vers la page du formulaire, et lisez la prop errors dans sa réponse. Le nom du champ refusé et son message s'y trouvent. Ajoutez un InputError pour ce champ, ou corrigez la valeur envoyée.

Une fenêtre avec une page d'erreur au-dessus de la vôtre

Une erreur dans votre code PHP produit une erreur 500. En local, Inertia affiche la page d'erreur de Laravel dans une fenêtre, par-dessus votre page. Lisez le message en haut, puis le nom du fichier et le numéro de ligne : ils désignent la ligne qui a échoué, souvent une faute de frappe dans le contrôleur ou une colonne absente de #[Fillable].

Le même message est écrit dans le journal de Laravel. Sur macOS et Linux, composer run dev lance un processus logs qui l'affiche dans le terminal. Sous Windows, ce processus n'existe pas : ouvrez storage/logs/laravel.log et lisez les dernières lignes.

Une erreur 404, ou un projet vide

La liaison de modèle cherche le projet par le paramètre {project} de la route. Un numéro absent de la base donne une 404 : vérifiez l'adresse dans la barre du navigateur. Un argument mal nommé, comme Project $projet, ne donne pas d'erreur : Laravel vous passe un projet vide, sans id, et la page reste blanche. L'argument de edit(), update() et destroy() doit s'appeler $project.

Une page blanche

Une page blanche vient le plus souvent d'une erreur JavaScript. Ouvrez la console avec F12 : le message nomme le fichier et la ligne. Une TypeError sur update.form() ou edit() signale un projet sans id, vu à la section 3.

Avant de demander de l'aide, ayez sous les yeux le message exact, le fichier, et la requête dans l'onglet Réseau.

5. Pratique : modifier, puis supprimer (60 min)

Faites les deux parties dans l'ordre.

  1. Modifier, environ 35 minutes. edit() et update(), puis Edit.vue, puis le bouton « Modifier » dans la liste. Vérifiez chaque critère avant de passer à la suite.
  2. Supprimer, environ 25 minutes. destroy(), puis le bouton « Supprimer » et sa boîte de dialogue dans la liste.

Modifiez un vrai projet : corrigez le résumé d'un de vos projets seedés, ou complétez les liens du projet créé au chapitre 1.12. Puis créez un projet d'essai et supprimez-le.

6. À la maison

  • Terminez la modification et la suppression, et cochez chaque critère de la section 1.
  • Terminez votre planche de direction artistique du chapitre 1.09, si ce n'est pas encore fait. Elle se remet en PDF avec le portfolio, dans le devoir Teams du temps 1. Vous l'appliquerez à la page publique au chapitre 1.18.

Ce que votre portfolio contient maintenant

Un back-office complet pour vos projets : lister, créer, modifier, supprimer. Chaque action passe par un <Form>, valide ses champs avec la même Form Request, et revient à la liste avec une notification. La suppression demande une confirmation.

À retenir

  • ProjectController.update.form(project) et destroy.form(project) mettent la méthode PUT ou DELETE dans l'adresse, avec ?_method=, sans champ caché à écrire.
  • Wayfinder lit le numéro du projet dans sa clé id, qui doit donc figurer dans les props de la page.
  • :default-value donne sa valeur de départ à un champ non contrôlé, case à cocher comprise.
  • Une boîte de dialogue placée dans chaque ligne disparaît avec la ligne supprimée, sans code pour la fermer.
  • Une erreur de validation se lit dans la prop errors de l'onglet Réseau, et une erreur 500 dans la page d'erreur de Laravel ou dans storage/logs/laravel.log.