Chapitre 1.5 — Anatomie mentale d’une requête Symfony
Partie 1 : Découvrir Symfony — Chapitre 5/5 Version de référence : Symfony 7.4 LTS (novembre 2025, PHP 8.2+).
⏱️ TL;DR
- Une requête HTTP entre par un seul fichier, le front controller
public/index.php, et ressort sous forme d’un objetResponse.- Entre les deux, le
Kernel(via le composantHttpKernel) orchestre un pipeline d’événements :kernel.request→ routing → résolution du contrôleur → résolution des arguments → exécution du contrôleur →kernel.response→ envoi →kernel.terminate.- Le principe :
Request → (votre contrôleur) → Response. Tout le framework n’est que « ce qui aide à transformer une Request en Response ».- L’arborescence est conventionnelle et stable :
config/,src/,templates/,public/,var/,bin/console,vendor/. Vous saurez toujours où chercher.- Pour un dev Next.js : c’est le middleware + routing + route handler de Next, mais rendu explicite et branchable (des listeners d’événements) au lieu d’être caché dans le framework.
- Gardez ce schéma en tête : tout le reste du cours s’y accroche.
🎯 Objectifs
- Décrire le trajet complet d’une requête, du front controller à la réponse.
- Nommer les événements du noyau et savoir à quoi chacun sert.
- Reconnaître le rôle du
Kernel, du routing, du contrôleur et de laResponse. - Vous repérer dans l’arborescence d’un projet Symfony sans hésiter.
1. L’idée fondatrice : Request in, Response out
Tout Symfony tient dans une phrase :
Une application web reçoit une
Request(objet PHP qui représente la requête HTTP) et doit produire uneResponse(objet PHP qui représente la réponse HTTP). Tout le framework existe pour aider à cette transformation.
Ces deux objets viennent du composant HttpFoundation : ils encapsulent les superglobales PHP ($_GET, $_POST, $_SERVER, en-têtes, cookies…) dans une API propre et testable. Votre contrôleur est la fonction qui, au milieu, reçoit la Request et renvoie la Response.
💡 Pour un dev Next.js : c’est exactement le contrat d’un Route Handler :
export async function GET(request: Request): Promise<Response>. Symfony généralise ce contrat à toute l’application et rend explicite tout ce qui se passe autour (routing, middleware, sérialisation).
2. Le point d’entrée unique : le front controller
Contrairement au vieux PHP où chaque URL pointait vers un fichier .php différent, Symfony utilise un front controller : toutes les requêtes passent par un seul fichier, public/index.php. Le serveur web (Nginx, FrankenPHP…) redirige tout vers lui.
// public/index.php — le seul fichier PHP exposé au web
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};Ce fichier ne fait presque rien : il charge l’autoloader de Composer et délègue à Symfony Runtime, qui instancie le Kernel et lui passe la main. C’est volontairement minuscule — toute la logique est ailleurs.
⚠️ Piège : seul le dossier
public/est exposé au web (c’est la racine web du serveur). Tout le reste —src/,config/,.env,vendor/— est hors de la racine web, donc inaccessible depuis un navigateur. Ne placez jamais de code sensible danspublic/. (Si vous connaissez le cours Moodle : même principe que la racinepublic/de Moodle 5.1+.)
⚡ Depuis Symfony 5 : le
public/index.phpa été simplifié avec Symfony Runtime (l’autoload_runtime.phpet la closure qui renvoie le Kernel). En Symfony 5 début,index.phpcontenait encore leRequest::createFromGlobals()et l’envoi manuel de la réponse. Aujourd’hui, Runtime abstrait ça (et permet aussi FrankenPHP, RoadRunner, etc. sans changer votre code).
3. Le Kernel et le pipeline d’événements
Le Kernel (src/Kernel.php dans votre projet) est le chef d’orchestre. Il s’appuie sur le composant HttpKernel, dont la méthode handle(Request): Response déroule un pipeline d’événements. Comprendre ce pipeline, c’est comprendre Symfony.
3.1 Les événements du noyau, un par un
| Événement | Quand | À quoi ça sert (exemples) |
|---|---|---|
kernel.request | Tout début | Déterminer la route, la locale, l’authentification (le firewall de sécurité écoute ici). |
kernel.controller | Contrôleur trouvé, pas encore appelé | Modifier le contrôleur, vérifier des droits, initialiser des choses. |
| (résolution des arguments) | Juste avant l’appel | Les value resolvers transforment la requête en arguments typés du contrôleur (ex. #[MapEntity] Booking $booking). |
kernel.view | Le contrôleur a renvoyé autre chose qu’une Response | Convertir la valeur retournée en Response (ex. API Platform sérialise un objet en JSON). |
kernel.response | Réponse prête, avant envoi | Modifier les en-têtes, injecter la barre de debug, gérer le cache HTTP, le CORS. |
kernel.exception | Une exception a été levée | Transformer l’erreur en réponse propre (page 404/500, JSON d’erreur). |
kernel.terminate | Après l’envoi de la réponse | Tâches lourdes différées (envoi d’e-mails, logs) sans faire attendre l’utilisateur. |
💡 Pour un dev Next.js : ce pipeline, c’est l’équivalent des middlewares de Next/Express, mais structuré en phases nommées et ouvert : n’importe quel service peut « écouter » un événement pour intervenir (on appelle ça un event listener / subscriber, Partie 5). L’authentification, le CORS, le cache, la barre de debug… tout est branché là, de façon inspectable — pas de magie cachée.
4. Routing → Contrôleur → Response, concrètement
Le trio que vous manipulerez tous les jours :
-
Le routing associe une URL (et une méthode HTTP) à un contrôleur. En Symfony 7.4, on le déclare par attribut directement au-dessus de la méthode :
// src/Controller/BookingController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class BookingController extends AbstractController { #[Route('/bookings/{id}', name: 'booking_show', methods: ['GET'])] public function show(int $id): Response { return $this->render('booking/show.html.twig', ['id' => $id]); } } -
Le contrôleur est votre code : une méthode qui reçoit des arguments (résolus depuis la requête) et renvoie une
Response. Ici,render()produit une réponse HTML via Twig ; pour une API, on renverrait du JSON. -
La
Responserepart dans le pipeline (kernel.response) puis est envoyée au navigateur.
⚡ Depuis Symfony 5 : en Symfony 5, ce
#[Route(...)]était une annotation dans un commentaire (/** @Route("/bookings/{id}") */) nécessitantdoctrine/annotations. En 7.4, c’est un attribut PHP natif (#[Route(...)]) — plus rapide, validé par l’IDE, sans dépendance annexe. Le paramètre de route{id}est, lui, inchangé dans l’esprit.
5. Où vit quoi : l’arborescence d’un projet
L’un des grands conforts de Symfony : la structure est conventionnelle. Dans n’importe quel projet Symfony 7.4, vous trouverez :
mon-projet/
├── bin/
│ └── console ← l'outil CLI (bin/console ...) : générer, migrer, debug
├── config/
│ ├── packages/ ← config de chaque bundle (framework.yaml, doctrine.yaml…)
│ ├── routes/ ← routing (quand pas en attributs)
│ ├── services.yaml ← configuration du conteneur de services
│ └── bundles.php ← liste des bundles activés
├── migrations/ ← migrations de base de données (Doctrine)
├── public/
│ └── index.php ← LE front controller (seul dossier exposé au web)
├── src/ ← VOTRE code (namespace App\)
│ ├── Controller/ ← les contrôleurs
│ ├── Entity/ ← les entités Doctrine (le modèle de données)
│ ├── Repository/ ← les requêtes vers la base
│ ├── Service/ ← vos services métier
│ └── Kernel.php ← le noyau de l'application
├── templates/ ← les templates Twig (.html.twig)
├── tests/ ← les tests (PHPUnit)
├── translations/ ← fichiers de traduction (i18n)
├── var/
│ ├── cache/ ← cache généré (ne pas committer)
│ └── log/ ← journaux
├── vendor/ ← dépendances installées par Composer (ne pas committer)
├── .env ← variables d'environnement (valeurs par défaut)
├── composer.json
└── composer.lockQuelques repères mentaux :
src/= votre code. C’est là que vous passez 90 % du temps.config/= comment le framework et les bundles sont réglés.templates/= le HTML (Twig), pour la partie full-stack.public/= la seule porte web ; le reste est privé.var/etvendor/= généré/installé, jamais committé (déjà dans.gitignore).bin/console= votre couteau suisse en ligne de commande (on l’utilisera dès la Partie 3).
⚠️ Piège :
var/cache/peut contenir un cache « périmé » qui provoque des comportements étranges après un changement de config. Le réflexe :php bin/console cache:clear. (On verra que le cache Symfony est très agressif — c’est une force en prod, une source de confusion en dev quand on l’oublie.)
6. La carte à garder en tête
Si vous ne devez retenir qu’un schéma de toute la Partie 1, c’est celui-ci — chaque partie suivante viendra en détailler une case :
Vous avez maintenant la carte. La Partie 2 va vous redonner le langage (PHP moderne) ; la Partie 3 fera tourner un vrai projet ; la Partie 4 ouvrira la case « Kernel / Routing / Contrôleur ». Tout est en place.
✏️ Exercices
Exercice 1. En une phrase, résumez ce que « fait » Symfony, en utilisant les mots Request et Response.
✅ Solution
Symfony est l’ensemble des outils qui aident à transformer une Request (requête HTTP entrante) en Response (réponse HTTP sortante), via un pipeline d’événements qui route l’URL vers votre contrôleur et met en forme ce qu’il renvoie.
Exercice 2. Associez chaque besoin à l’événement du noyau le plus pertinent : (a) vérifier que l’utilisateur est authentifié ; (b) ajouter un en-tête CORS à la réponse ; (c) envoyer un e-mail sans faire attendre l’utilisateur ; (d) transformer un objet renvoyé par le contrôleur en JSON.
✅ Solution
(a) kernel.request (le firewall de sécurité agit tôt, avant le contrôleur). (b) kernel.response (la réponse est prête, on modifie ses en-têtes avant envoi). (c) kernel.terminate (après l’envoi de la réponse, pour ne pas faire attendre le client). (d) kernel.view (le contrôleur a renvoyé un objet, pas une Response : on le sérialise — c’est ce que fait API Platform).
Exercice 3. Un collègue place un fichier secret.php contenant des clés d’API dans public/. Pourquoi est-ce dangereux, et où devrait-il aller ?
✅ Solution
public/ est la racine web : tout ce qui s’y trouve est accessible depuis un navigateur (https://site/secret.php), donc potentiellement exposé. Les secrets ne doivent jamais y être. Ils doivent vivre hors de la racine web : variables d’environnement (.env/.env.local, non committées) ou le coffre de secrets de Symfony (Partie 15). Le code sensible va dans src/ ; la config dans config/.
Exercice 4. Dans l’arborescence, où iriez-vous chercher : (a) la définition de l’entité Booking ; (b) la config de Doctrine ; (c) un template HTML ; (d) la commande pour vider le cache ?
✅ Solution
(a) src/Entity/Booking.php. (b) config/packages/doctrine.yaml. (c) templates/…/*.html.twig. (d) via bin/console : php bin/console cache:clear.
🧠 Quiz de révision
1. Par quel fichier unique entrent toutes les requêtes d’une app Symfony ?
Le front controller public/index.php, seul fichier PHP exposé au web ; il délègue au Kernel via Symfony Runtime.
2. Quel objet le composant HttpKernel prend-il en entrée, et que renvoie-t-il ?
Il prend une Request (méthode handle(Request)) et renvoie une Response.
3. À quoi sert l’événement kernel.terminate ?
kernel.terminate ?À exécuter des tâches après l’envoi de la réponse (e-mails, logs lourds) pour ne pas faire attendre l’utilisateur.
4. En Symfony 7.4, comment déclare-t-on une route au-dessus d’une méthode de contrôleur ?
Avec un attribut PHP : #[Route('/chemin', name: '…', methods: ['GET'])] (et non plus une annotation dans un commentaire comme en Symfony 5 début).
5. Quels dossiers ne committe-t-on jamais, et pourquoi ?
vendor/ (dépendances réinstallables via composer install) et var/ (cache et logs générés). Ils sont dans .gitignore.
Fin de la Partie 1. Vous avez la carte : ce qu’est Symfony, son histoire, son écosystème, sa place face au reste, et le trajet d’une requête.
Prochaine étape : Partie 2 — PHP moderne pour dev JS/TS — on récupère le langage (types, enums, attributs, readonly, Composer/PSR-4…) avant de plonger dans le code Symfony.