Tutoriel MVC · Chapitre 10

Assembler
l’application MVC.

Reliez chaque couche et terminez le parcours CRUD de l’application de tâches.

REQUESTUSE CASESTATERESPONSE

Toutes les pièces existent. Construisons maintenant le parcours complet.

Les chapitres précédents ont isolé routes, contrôleurs, vues, stockage et modèle afin de comprendre chaque responsabilité. Nous allons les relier dans une application de tâches utilisable sans abandonner cette séparation au moment où le projet devient concret.

Vous suivrez chaque requête depuis le clic jusqu’à SQLite, puis depuis la lecture du modèle jusqu’au HTML. Cette vision verticale rend les erreurs localisables, les résultats testables et les fonctionnalités évolutives.

ROUTECONTROLLERMODELVIEWRESPONSE
Résultat du chapitre

Une application complète capable de lister, créer, modifier, terminer, rouvrir et supprimer des tâches, avec des 404 cohérentes et un parcours vérifié de bout en bout.

10.1

Relier sans mélanger

Une application MVC devient utile lorsque chaque couche coopère tout en gardant sa responsabilité. La route reconnaît l’intention HTTP, le contrôleur orchestre le cas d’usage, le modèle manipule l’état durable et la vue transforme le résultat en interface.

Relier les couches ne signifie pas les fusionner. Une vue qui exécute du SQL, un modèle qui fabrique une redirection ou une route qui contient toute la création rendent chaque évolution plus risquée.

Le parcours reste lisible dans un seul sens : requête, middlewares, route, action, modèle et réponse. Cette direction permet de localiser une erreur sans parcourir tout le projet.

À retenir

Une couche appelle la suivante par un contrat clair sans prendre sa responsabilité.

10.2

Cartographier la ressource Task

Avant d’implémenter, listez les intentions : afficher la collection, ouvrir les formulaires, créer, modifier, terminer, rouvrir et supprimer. Cette liste devient le contrat public de la ressource.

Les URL désignent la ressource et les méthodes HTTP décrivent l’intention. GET observe, POST crée, PATCH modifie et DELETE supprime. Une surcharge de méthode peut représenter PATCH ou DELETE depuis un formulaire HTML.

Conservez ces déclarations dans routes/webapp.php. Le fichier devient une carte compacte du produit, jamais un second contrôleur.

À retenir

La table des routes doit expliquer le produit sans révéler son implémentation.

phpaml — zsh
$router->get('/tasks', [TaskController::class, 'index']);
$router->get('/tasks/create', [TaskController::class, 'create']);
$router->post('/tasks', [TaskController::class, 'store']);
$router->get('/tasks/{id}/edit', [TaskController::class, 'edit']);
$router->patch('/tasks/{id}', [TaskController::class, 'update']);
$router->post('/tasks/{id}/toggle', [TaskController::class, 'toggle']);
$router->delete('/tasks/{id}', [TaskController::class, 'destroy']);
10.3

Afficher la liste

index récupère seulement les filtres autorisés, demande au modèle une collection ordonnée, puis transmet à la vue des données prêtes à présenter. Le contrôleur ne fabrique aucune carte HTML.

La vue reçoit tasks et filter. Elle affiche les tâches, les actions disponibles et un état vide utile sans connaître PDO ni la structure exacte de la requête.

Imposez un ordre stable et limitez la collection. L’interface doit rester prévisible avec zéro, une ou plusieurs centaines de tâches.

À retenir

Préparez dans le contrôleur; présentez dans la vue.

phpaml — zsh
public function index(Request $request): Response
{
    $filter = $request->query('filter', 'all');
    $tasks = $this->tasks->forFilter($filter);
    return $this->view('tasks/index', compact('tasks', 'filter'));
}
10.4

Créer avec Post/Redirect/Get

GET sert le formulaire et POST reçoit son envoi. store lit les champs, effectue une première validation, demande au modèle de créer, écrit un message flash puis redirige vers la liste.

Cette séquence Post/Redirect/Get empêche une actualisation de soumettre deux fois la même tâche. Le navigateur termine sur une URL GET stable, partageable et actualisable.

En cas d’erreur, rendez le formulaire avec les anciennes valeurs et un statut 422. Le chapitre 11 structurera davantage la validation et ajoutera CSRF.

À retenir

Après une écriture réussie, redirigez vers une lecture stable.

phpaml — zsh
public function store(Request $request): Response
{
    $title = trim((string) $request->input('title'));
    if ($title === '') {
        return $this->view('tasks/create', ['error' => 'Title is required.'], 422);
    }
    $task = $this->tasks->create(['title' => $title]);
    $this->flash->success("Task #{$task->id} created.");
    return $this->redirect('/tasks');
}
10.5

Modifier sans duplication

L’édition possède deux temps : GET charge la tâche et ses valeurs actuelles; PATCH reçoit les nouvelles valeurs et applique la transition. Les deux actions utilisent la même convention pour une ressource absente.

Centralisez la recherche dans findOrFail, un resolver ou une petite méthode privée. Copier le même test null dans chaque action finit par produire des réponses incohérentes.

Une mise à jour sans changement ne doit jamais fabriquer UPDATE table SET WHERE. Le modèle peut la considérer comme réussie sans requête et le contrôleur conserve un parcours normal.

À retenir

Factorisez la résolution de la ressource, pas les responsabilités des actions.

10.6

Terminer et rouvrir

Terminer une tâche est une transition métier et non une colonne modifiée n’importe où. Les méthodes complete et reopen sont faciles à autoriser, journaliser et tester.

Une route toggle peut choisir la transition selon l’état actuel, mais le contrôleur délègue ensuite au modèle. Il ne manipule pas directement completed.

Le message flash décrit le résultat observé. Après la transition, redirigez vers une destination validée ou vers /tasks par défaut.

À retenir

Nommez les transitions selon le métier et protégez leurs invariants dans le modèle.

10.7

Supprimer avec précaution

La suppression est irréversible pour l’utilisateur. L’interface doit la distinguer d’une navigation et demander une confirmation progressive lorsque le risque le justifie.

Le serveur ne dépend pourtant jamais de JavaScript pour être correct. DELETE retrouve une cible précise, vérifie le résultat, ajoute un message puis redirige.

N’utilisez jamais GET pour détruire. Robots, préchargements et aperçus suivent des liens; une lecture ne doit provoquer aucun changement durable.

À retenir

Une action destructive exige une méthode adaptée, une cible précise et un résultat vérifié.

10.8

Répondre 404 proprement

Une URL peut viser une tâche supprimée, un identifiant inventé ou une ressource inaccessible. Cette absence appartient au fonctionnement normal du Web; elle ne doit produire ni erreur fatale ni formulaire vide.

Le résolveur lève NotFoundException, puis le gestionnaire choisit la représentation : page HTML pour le navigateur ou objet JSON pour une API.

Une réponse uniforme évite aussi de révéler l’existence d’une ressource privée. Le contrôleur reste concentré sur le parcours réussi.

À retenir

Une ressource absente est un résultat HTTP attendu, pas une panne.

10.9

Tester le parcours complet

Testez une succession d’états observables : liste vide, création, modification, terminaison, réouverture puis suppression. Votre scénario raconte la vie entière d’une tâche.

À chaque étape, vérifiez le statut HTTP, la redirection, le message visible et la ligne en base. Un test limité à 302 peut rester vert alors qu’aucune tâche n’a été enregistrée.

Ajoutez les chemins négatifs : titre vide, identifiant absent, mise à jour sans changement et double suppression. Ils révèlent les défauts d’intégration cachés par le parcours idéal.

À retenir

Un test d’intégration prouve que plusieurs couches produisent ensemble le bon résultat.

10.10

Relever le défi due_at

Ajoutez une date limite en faisant évoluer verticalement toute la fonctionnalité : migration, modèle, contrôleur, formulaires, rendu et tests. N’abandonnez aucune couche dans un état intermédiaire.

La règle overdue appartient au modèle : une tâche est en retard lorsque sa date est passée et qu’elle n’est pas terminée. La couleur utilisée pour la signaler appartient uniquement à la vue.

Cette séparation permet de réutiliser la même règle dans une API, un courriel ou une commande. Elle montre qu’une architecture claire accélère les ajouts au lieu de les ralentir.

À retenir

Une fonctionnalité est terminée lorsqu’elle traverse proprement toutes les couches concernées.

Atelier guidé

Assemblez et vérifiez TaskFlow.

  1. Déclarez les sept routes Task.
  2. Implémentez index et son état vide.
  3. Reliez create et store avec PRG.
  4. Ajoutez edit et update sans dupliquer la recherche.
  5. Implémentez complete et reopen.
  6. Ajoutez la suppression avec confirmation.
  7. Transformez toute tâche absente en 404.
  8. Testez le CRUD complet puis due_at.

Correction raisonnée

Suivez une tâche pendant toute sa vie.

Créez « Préparer la démonstration », retrouvez son identité en base, modifiez le titre, terminez-la, rouvrez-la puis supprimez-la. Après chaque requête, contrôlez statut HTTP, destination, message et ligne SQLite. Si les quatre observations racontent la même histoire, vos couches sont correctement reliées.

01La route reconnaît l’intention
02Le contrôleur orchestre
03Le modèle garantit l’état
04La vue rend le résultat
05Le test confirme le parcours

En résumé

Votre première application PHPAML MVC fonctionne de bout en bout.

Les routes décrivent les intentions, TaskController orchestre, Task protège l’état, les vues présentent les résultats et chaque écriture revient vers une lecture stable grâce à Post/Redirect/Get.

  • Gardez les dépendances dans un seul sens.
  • Associez chaque intention à la bonne méthode HTTP.
  • Centralisez la résolution des ressources absentes.
  • Vérifiez réponse, interface et base dans un même scénario.
  • Faites évoluer une fonctionnalité verticalement.

Au chapitre 11, nous renforcerons l’application avec validation structurée, CSRF, sessions, en-têtes de sécurité, pages d’erreur et journaux.

Chapitre 09Chapitre 11 · À venir 🔒