Leçon 18 / 20

Créer les parcours de gestion

35 min PHP 8 — architecture, tests et sécurité Gratuit

Objectif : Relier les formulaires d’ajout, de modification et de suppression aux cas d’usage sécurisés du carnet.

Ajouter, modifier ou supprimer un contact ne consiste pas à relier un bouton à une requête SQL. Chaque action doit passer par l’authentification, la vérification CSRF et les règles métier avant toute persistance. Dans cette leçon, vous branchez ces parcours sur le carnet de contacts et retournez à la liste avec un message de résultat.

Le parcours de consultation et le rendu séparé ont été construits dans la leçon précédente ; vous pouvez vous y reporter pour leur assemblage. Ici, vous ajoutez les actions de gestion au même contrôleur, sans déplacer la validation métier dans le code HTTP.

Faire correspondre chaque action à sa méthode HTTP

Les formulaires d’ajout et de modification envoient leurs données en POST vers /contacts. Le formulaire de suppression envoie également un POST, vers /contacts/supprimer. Une requête GET sert à consulter : elle ne doit jamais déclencher une mutation, même si son URL contient un identifiant.

Coordonner les traitements dans le contrôleur

Le contrôleur reçoit une requête, vérifie d’abord l’accès au carnet, puis valide le jeton CSRF avant de lire les champs destinés à une mutation. Il vérifie ensuite la forme des données reçues et délègue au service métier. Le dépôt, déjà injecté dans le service, conserve la responsabilité de la persistance.

src/Controleur/CarnetControleur.phpPHP
<?php

declare(strict_types=1);

namespace Atelier\Controleur;

use Atelier\Securite\JetonCsrf;
use Atelier\Service\ContactService;
use Atelier\Vue\Rendu;
use InvalidArgumentException;
use Throwable;

final class CarnetControleur
{
    public function __construct(
        private readonly ContactService $contactService,
        private readonly JetonCsrf $jetonCsrf,
        private readonly Rendu $rendu
    ) {
    }

    public function creer(array $session, array $post): array
    {
        return $this->traiterContact($session, $post);
    }

    public function supprimer(array $session, array $post): array
    {
        if (!$this->estAuthentifiee($session)) {
            return $this->reponse(401, 'Authentification requise.');
        }

        if (!$this->jetonCsrf->estValide($post['jeton_csrf'] ?? null)) {
            return $this->reponse(403, 'Requête refusée.');
        }

        $id = filter_var($post['id'] ?? null, FILTER_VALIDATE_INT);
        if ($id === false || $id < 1) {
            return $this->reponse(400, 'Identifiant invalide.');
        }

        try {
            $this->contactService->supprimer($id);
        } catch (Throwable) {
            return $this->reponse(400, 'Impossible de supprimer ce contact.');
        }

        return $this->redirigerAvecMessage('Contact supprimé.');
    }

    public function enregistrer(array $session, array $post): array
    {
        return $this->traiterContact($session, $post);
    }

    private function traiterContact(array $session, array $post): array
    {
        if (!$this->estAuthentifiee($session)) {
            return $this->reponse(401, 'Authentification requise.');
        }

        if (!$this->jetonCsrf->estValide($post['jeton_csrf'] ?? null)) {
            return $this->reponse(403, 'Requête refusée.');
        }

        $nom = $post['nom'] ?? null;
        $courriel = $post['courriel'] ?? null;
        $telephone = $post['telephone'] ?? '';
        $idBrut = $post['id'] ?? '';

        if (!is_string($nom) || !is_string($courriel) || !is_string($telephone)) {
            return $this->reponse(400, 'Données de formulaire invalides.');
        }

        $id = null;
        if ($idBrut !== '') {
            $id = filter_var($idBrut, FILTER_VALIDATE_INT);
            if ($id === false || $id < 1) {
                return $this->reponse(400, 'Identifiant invalide.');
            }
        }

        try {
            $this->contactService->enregistrer($nom, $courriel, $telephone, $id);
        } catch (InvalidArgumentException) {
            return $this->reponse(422, 'Vérifiez les informations du contact.');
        } catch (Throwable) {
            return $this->reponse(400, 'Impossible d’enregistrer ce contact.');
        }

        $message = $id === null ? 'Contact ajouté.' : 'Contact modifié.';
        return $this->redirigerAvecMessage($message);
    }

    private function estAuthentifiee(array $session): bool
    {
        return ($session['authentifiee'] ?? false) === true;
    }

    private function redirigerAvecMessage(string $message): array
    {
        return [
            'statut' => 303,
            'entetes' => ['Location' => '/'],
            'session' => ['message' => $message],
            'corps' => '',
        ];
    }

    private function reponse(int $statut, string $message): array
    {
        return [
            'statut' => $statut,
            'entetes' => [],
            'session' => [],
            'corps' => $this->rendu->message($message),
        ];
    }
}

Dans votre routeur, appelez creer() pour POST sur /contacts et supprimer() pour POST sur /contacts/supprimer. La même route /contacts peut traiter l’ajout et la modification : un champ id vide indique un ajout, un identifiant positif une modification. Le point d’entrée HTTP doit transférer le statut, les en-têtes et le corps de la réponse retournée par le contrôleur.

Retourner au carnet après une mutation

Après un traitement réussi, la réponse 303 indique au navigateur de demander la page du carnet avec GET. Cette redirection évite qu’un rafraîchissement répète l’envoi du formulaire. Le message est placé dans la session par le contrôleur ; le point d’entrée le récupère une seule fois, puis le transmet au rendu, qui doit continuer à échapper les valeurs affichées.

Pour les erreurs, renvoyez un statut adapté et un message compréhensible, sans inclure une exception PDO ou une trace technique dans la réponse. En production, consignez les détails dans un journal serveur protégé. Un identifiant manquant, un jeton invalide et des données refusées par les règles métier ne doivent jamais entraîner d’appel de mutation au dépôt.

À retenir

  • Utilisez POST pour toutes les mutations ; GET reste réservé à la consultation.
  • Vérifiez l’authentification et le jeton CSRF avant de déléguer une action au service.
  • Validez la forme des champs HTTP et laissez le service appliquer les règles métier.
  • Après succès, redirigez avec le statut 303 et affichez un message échappé depuis le rendu.
  • Ne renvoyez jamais les détails techniques d’une erreur interne au navigateur.

Dans la prochaine leçon, vous intégrerez la recherche au cas d’usage et vérifierez que ses résultats restent corrects.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.