Module 1 / 8 · 90 min
HTTP et cycle de requête
Identifier la responsabilité de chaque étape jusqu’à la réponse.
Comprendre le mécanisme
HttpFoundation représente requêtes et réponses ; HttpKernel orchestre leur traitement. Le routage identifie un contrôleur, les résolveurs préparent ses arguments puis le contrôleur produit une réponse. Des événements du kernel permettent d’intervenir à des étapes précises, notamment pour traiter une exception ou ajuster une réponse. Un subscriber déclare les événements qu’il écoute ; les priorités ordonnent des traitements, sans remplacer un contrat fonctionnel explicite.
Raisonner et vérifier
Distinguez le protocole HTTP de l’état métier. Un GET doit conserver la sémantique de lecture attendue ; une erreur de validation n’est pas une erreur interne. Une redirection renvoie au client une autre destination, tandis qu’une sous-requête interne reste un mécanisme serveur. Pensez également à la négociation de contenu, aux cookies et au cache lorsqu’une même URL peut avoir plusieurs représentations.
Du message HTTP aux objets du framework
HttpFoundation fournit notamment les objets Request et Response. HttpKernel organise leur cycle de traitement pour transformer une demande en réponse selon les composants configurés. Les deux responsabilités sont distinctes : disposer d’un objet Request ne valide pas encore son contenu et disposer d’un contrôleur ne décrit pas tous les événements qui peuvent intervenir autour de lui.
Understand this common mistake Le contrôleur écrit toujours directement toute réponse réseau.
Le cycle peut produire ou transformer une Response à plusieurs étapes.
Lire la chronologie des événements
Un listener peut intervenir avant la résolution ou l’exécution du contrôleur selon l’événement auquel il participe. L’ordre et les priorités deviennent donc une partie du comportement réel. Pour diagnostiquer un refus, notez l’étape atteinte et les effets observés. Ajouter un log uniquement dans l’action ne permet pas de conclure qu’aucune demande n’a traversé les couches précédentes.
Understand this common mistake Tout listener s’exécute nécessairement après la réponse métier.
Le point d’intervention dépend de l’événement.
Une réponse anticipée termine un chemin différent
Lorsqu’un traitement amont fournit une Response selon le cycle du kernel, le contrôleur peut ne pas être appelé. Cette possibilité sert à des refus ou réponses précoces, mais exige de placer les contrôles au bon endroit. Un test doit vérifier le statut et l’absence des effets de l’action évitée ; constater un message d’erreur ne prouve pas à lui seul cette chronologie.
Understand this common mistake Chaque requête appelle toujours l’action de route.
Un traitement amont peut terminer le cycle plus tôt.
Transformer une exception sans exposer la cause
Le traitement d’erreur peut produire une réponse adaptée à partir d’une exception. La représentation publique doit rester contrôlée et utile, tandis qu’un diagnostic privé peut conserver la cause avec une corrélation. Ne convertissez pas toute erreur en succès nominal. Vérifiez aussi les erreurs inattendues : leur chemin de rendu ne doit pas devenir une fuite de trace, de chemins ou de credentials.
Understand this common mistake Toute exception doit afficher sa trace au navigateur.
Le cycle d’erreur doit appliquer une politique publique adaptée.
Distinguer requête principale et sous-requête
Une sous-requête interne n’a pas nécessairement le même rôle qu’une nouvelle demande réseau principale. Un listener qui applique aveuglément une action à chaque événement peut donc répéter un traitement ou utiliser un mauvais contexte. Examinez les capacités du cycle ciblé et les cas auxquels le listener doit participer. Une redirection HTTP, au contraire, conduit normalement le client à effectuer une nouvelle requête vers sa destination.
Understand this common mistake Tout événement de requête concerne uniquement un appel réseau principal.
Le contexte de traitement peut différer.
État de requête et services réutilisés
Un service peut vivre au-delà d’une seule opération logique, notamment selon le mode d’exécution et les workers. Conserver l’utilisateur courant dans un champ mutable partagé peut contaminer un autre parcours. Passez le contexte utile explicitement ou utilisez les abstractions prévues avec un cycle maîtrisé. Le test doit exercer deux contextes successifs plutôt que seulement un appel avec une identité favorable.
Understand this common mistake Un service partagé peut mémoriser tout utilisateur sans durée de vie explicite.
Les cycles de service et de requête ne sont pas équivalents.
Valider les données de Request
L’objet Request organise des entrées de transport : paramètres, corps et métadonnées restent à interpréter selon leur provenance. Le framework ne rend pas tout champ authentique parce qu’il est accessible via une API pratique. Définissez format, bornes et autorité de chaque valeur avant son usage métier. La résolution d’un objet ou d’un identifiant ne donne pas automatiquement le droit de le consulter.
Understand this common mistake Toute valeur de Request est authentifiée par le framework.
L’objet de transport ne rend pas l’entrée fiable.
Construire une réponse contractuelle
Response porte un statut, des champs et un contenu. Leur combinaison doit représenter le résultat du parcours. Une demande acceptée pour un traitement encore asynchrone peut relever de 202 lorsque le contrat explique cette attente. Un contenu JSON contenant true ne prouve pas la fin de tous les effets. Les tests doivent examiner ce que le client peut déduire du message entier.
Understand this common mistake Le contenu suffit toujours à exprimer le résultat protocolaire.
Statut et en-têtes participent au contrat HTTP.
Le cache peut modifier le chemin observé
Une réponse réutilisée par un cache peut éviter une partie du traitement d’origine. Le contrôleur n’est donc pas nécessairement réexécuté à chaque consultation visible par le navigateur. Pour des données personnalisées, définissez clé, portée et invalidation selon les identités pertinentes. Une capture nominale ne démontre pas que le même contenu ne sera jamais partagé avec un autre acteur.
Understand this common mistake Toute réponse cachée rejoue le contrôleur.
La réutilisation peut se produire avant l’application.
Prouver l’ordre et le non-effet
Pour un refus avant mutation, vérifiez simultanément la réponse et les données ou appels aval qui doivent rester intacts. Cette preuve relie un mécanisme du kernel à son objectif réel. Préparez un cas autorisé pour éviter de tester seulement un système qui refuse tout. Conservez la version et le chemin d’exécution pertinent afin qu’un autre développeur puisse reproduire la décision.
Understand this common mistake Un simple corps de réponse prouve l’ordre de tous les listeners.
La chronologie et les effets demandent des observations adaptées.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpFoundation\Response;
function rejectEarly(RequestEvent $event): void {
$event->setResponse(new Response('Unavailable', 503));
}
Le listener de l’atelier reçoit un RequestEvent et lui associe une Response 503. Dans le contexte indiqué avant la résolution du contrôleur, cette réponse peut être utilisée comme résultat anticipé. Elle ne supprime pas des lignes de base et ne change pas automatiquement le statut en 200. La propriété importante est le chemin du cycle qui s’arrête avant l’action prévue.
Dans un test de laboratoire, donnez au contrôleur un collaborateur qui enregistrerait une écriture, puis vérifiez que ce collaborateur n’est pas appelé lorsque le refus anticipé s’applique. Ajoutez ensuite un cas nominal sans refus. Vous obtenez une preuve discriminante du cycle plutôt qu’une simple assertion sur un corps de réponse identique dans toutes les situations.
Worked example
<?php
use Symfony\Component\HttpFoundation\JsonResponse;
$response = new JsonResponse(['status' => 'accepted'], 202);
$response->headers->set('Cache-Control', 'private, no-store');
202 signale ici une demande acceptée dont le traitement peut rester à terminer. L’en-tête interdit le stockage de cette réponse privée par les caches concernés.
Practical exercise
Dessinez le parcours d’une requête qui échoue dans le service métier et définissez la réponse publique attendue.
View a suggested solution
Localisez l’exception, sa traduction HTTP et le point de journalisation. Testez que la réponse ne contient pas de trace interne et que son statut correspond au contrat client.
- Situer les événements du kernel.
- Distinguer redirection et sous-requête.
- Vérifier méthode et statut ensemble.
Knowledge check
Quel composant fournit les objets Request et Response ?
Explained answer: HttpFoundation — Il modélise les messages HTTP.
Related lesson: HTTP et cycle de requête ↗
Reference documentation ↗ https://symfony.com/doc/8.0/components/http_kernel.html
Module 2 / 8 · 90 min
Conteneur, configuration et environnement
Rendre les dépendances explicites et distinguer valeurs de configuration.
Comprendre le mécanisme
Le conteneur construit et relie les services. L’autowiring utilise les types ; s’il existe plusieurs implémentations possibles, un alias ou une configuration explicite lève l’ambiguïté. Une décoration enveloppe un service pour étendre son comportement, alors qu’un tag le rend repérable par un mécanisme de collection ou compilation. Les paramètres scalaires et les variables d’environnement demandent un nom et un traitement adaptés.
Raisonner et vérifier
Une variable réellement définie dans l’environnement peut prendre le dessus sur les fichiers dotenv. Les fichiers spécifiques à l’environnement et leurs variantes locales ont un ordre de chargement défini ; inspectez l’origine effective sans afficher les secrets. Utilisez les commandes de diagnostic du conteneur et de configuration pour vérifier vos hypothèses. Un service doit demander ses dépendances utiles plutôt que recevoir tout le conteneur.
Assembler un graphe explicite
Le conteneur organise des services et leurs dépendances selon sa configuration. L’autowiring facilite la résolution de contrats de types, mais ne choisit pas automatiquement toutes les politiques métier. Dessinez le graphe utile au consommateur et donnez des responsabilités ciblées à ses collaborateurs. Une classe facilement instanciable avec des doubles de test expose généralement mieux ses besoins qu’un accès global à des dépendances cachées.
Understand this common mistake Autowiring crée automatiquement toute politique métier.
L’injection ne décide pas les permissions et invariants.
Résoudre une interface sans ambiguïté
Si plusieurs services implémentent une interface injectée, un alias approprié peut désigner explicitement l’implémentation attendue. Le nom du contrat ne suffit pas à déduire ce choix universel. Documentez notamment la différence entre fournisseur réel et laboratoire. Un test doit vérifier la résolution effective du conteneur, sans confondre ce choix technique avec le droit d’un utilisateur à déclencher l’opération correspondante.
Understand this common mistake Toute interface choisit toujours une implémentation unique sans configuration.
Plusieurs services peuvent satisfaire le même type.
Paramètres, valeurs et objets de service
Un paramètre peut représenter une valeur de configuration alors qu’un service possède un comportement et un cycle de vie. Ne supposez pas que tout élément du conteneur est un objet partagé. Définissez type, provenance et sens de la valeur attendue. Une chaîne lue depuis l’environnement peut être absente ou incorrecte ; la commodité de l’injection ne retire pas le besoin de valider le contrat utile.
Understand this common mistake Un paramètre est nécessairement un objet injecté partagé.
La configuration peut porter des valeurs et références différentes.
Environnements et surcharges de configuration
La configuration de test, de développement et de production peut différer. Un service correctement résolu dans un environnement ne prouve pas la même valeur ou le même transport dans l’autre. Reliez les surcharges au scénario voulu et vérifiez l’état effectif. Les diagnostics doivent montrer la configuration publique pertinente sans recopier les secrets ; afficher toutes les variables n’est pas une procédure de résolution acceptable.
Understand this common mistake La configuration test prouve nécessairement toute valeur prod.
Les surcharges et variables peuvent différer.
Durée de vie et état partagé
Un service partagé peut conserver un état entre plusieurs usages selon son environnement d’exécution. Il n’est pas automatiquement isolé par utilisateur. Évitez d’y mémoriser librement un tenant ou un contexte de requête. Si un état est nécessaire, attribuez-lui une portée et une procédure de remise à zéro adaptées. Les tests successifs avec deux identités peuvent révéler une fuite que le seul scénario nominal ne montre pas.
Understand this common mistake Shared signifie isolé automatiquement par utilisateur.
La même instance peut servir plusieurs usages selon son cycle.
Injection ciblée et décoration
Le constructeur rend visibles les contrats nécessaires à un service métier. Une décoration peut ensuite envelopper un comportement existant, par exemple ajouter une mesure autour d’un transport tout en conservant son contrat utile. Elle ne doit pas introduire des effets inattendus ou contourner les permissions. Testez le résultat observable du décorateur et les appels au collaborateur enveloppé, plutôt que supposer que l’assemblage garantit la sémantique.
Understand this common mistake Un accès global au conteneur rend toujours le contrat plus précis.
Il peut masquer les besoins réels de la classe.
Limiter la distribution des secrets
Un credential doit être accessible aux composants qui en ont besoin, avec un périmètre d’environnement et de finalité défini. Le placer dans une variable d’environnement ne rend pas son affichage public inoffensif. Vérifiez les logs, erreurs et artefacts qui peuvent conserver cette valeur. Une correction de visibilité future doit accompagner le retrait d’une capacité réellement compromise si elle a déjà été diffusée.
Understand this common mistake Une variable d’environnement est automatiquement sûre à afficher.
Son mode d’injection ne retire pas sa sensibilité.
Compiler le bon conteneur pour la release
Le cache de conteneur reflète un code et une configuration. Réutiliser aveuglément une ancienne compilation peut conserver des définitions qui ne correspondent plus au déploiement. La préparation de release doit suivre le cycle prévu par le framework et l’environnement réel. Un fichier copié avec succès ne démontre pas que tous les processus ont chargé les services et paramètres attendus.
Understand this common mistake Un cache d’une ancienne configuration reste toujours correct.
Les métadonnées compilées doivent suivre le code et l’environnement.
Versions, tags et traitements de compilation
Les possibilités d’une extension dépendent de la version ciblée et de ses contrats. Les tags peuvent identifier une famille de services pour un traitement de compilation ou un mécanisme prévu par le composant. Le tag seul ne prouve pas que le comportement métier est correct. Vérifiez qui le consomme, quelles données il attend et comment les services concernés sont assemblés dans le conteneur final.
Understand this common mistake Tout exemple de n’importe quelle version est interchangeable.
Les API et options peuvent évoluer.
Tester une résolution réellement utile
Un nom de service présent n’est qu’un début de preuve. Le parcours doit recevoir la bonne implémentation, les bons paramètres et les dépendances nécessaires au comportement attendu. Ajoutez un test de configuration ciblé puis des tests métier qui utilisent les contrats injectés. Une résolution qui n’instancie jamais la classe défectueuse peut laisser une erreur invisible jusqu’au premier appel réel.
Understand this common mistake Un nom de service présent prouve toutes ses valeurs réelles.
La définition doit être compatible et correctement configurée.
Atelier guidé : lire, expliquer et vérifier le code
services:
App\Billing\Gateway: '@App\Billing\SandboxGateway'
App\Billing\SandboxGateway: ~
App\Billing\LiveGateway: ~
L’alias de l’atelier lie Gateway à SandboxGateway dans ce conteneur. LiveGateway existe, mais n’est pas la cible désignée par cet alias. Le choix ne chiffre aucun credential et ne décide pas quels utilisateurs peuvent facturer. Il répond uniquement à la question de l’implémentation assemblée pour ce contrat.
Dans un environnement de laboratoire, obtenez le consommateur qui dépend de Gateway puis vérifiez, par son comportement observable, qu’il utilise le fournisseur sandbox prévu. Configurez ensuite un double de test pour contrôler les données transmises sans appel réseau. Si une décoration est ajoutée, vérifiez qu’elle conserve ce contrat et n’introduit pas une seconde facturation cachée. La preuve doit suivre l’assemblage effectif, pas seulement le nom d’une définition YAML.
Worked example
<?php
interface Notifier { public function send(string $message): void; }
final class ReservationService {
public function __construct(private readonly Notifier $notifier) {}
public function accept(): void { $this->notifier->send('accepted'); }
}
Le service dépend d’un contrat fin et peut recevoir un faux notifier en test. Le choix d’une implémentation doit être configuré dans le conteneur.
Practical exercise
Ajoutez deux implémentations de Notifier puis résolvez l’ambiguïté sans modifier le service métier.
View a suggested solution
Définissez un alias pour l’interface dans services.yaml. Vérifiez debug:autowiring et remplacez le service dans le conteneur de test. Testez l’envoi sans réseau.
- Résoudre les ambiguïtés explicitement.
- Distinguer tags, alias et décoration.
- Vérifier la configuration effective sans secrets.
Knowledge check
Deux services implémentent la même interface injectée. Quel mécanisme choisit explicitement celui voulu ?
Explained answer: Un alias de service approprié — L’alias fixe le service associé au contrat.
Related lesson: Conteneur, configuration et environnement ↗
Reference documentation ↗ https://symfony.com/doc/8.0/service_container.html
Module 3 / 8 · 90 min
Routes, contrôleurs et résolution d’arguments
Restreindre une route et produire une réponse contractuelle.
Comprendre le mécanisme
Une route associe chemin, paramètres et contraintes à un contrôleur. Les méthodes HTTP, hôtes ou requirements réduisent les correspondances autorisées. Une valeur par défaut n’est pas une validation métier. Les noms de routes servent à générer des URL indépendamment de la forme du chemin. Le contrôleur traduit le protocole vers les services applicatifs et retourne une réponse, sans absorber toute la logique métier.
Raisonner et vérifier
Un argument résolu automatiquement n’est pas nécessairement autorisé pour l’acteur courant. Vérifiez séparément format, existence et accès à la ressource. Une route statique peut entrer en conflit avec un paramètre générique ; les priorités et contraintes doivent rendre la sélection prévisible. Testez aussi les méthodes refusées et la locale utilisée par la génération de liens.
Une méthode de route n’est pas une permission
Restreindre une route à POST évite certains appels incompatibles avec son contrat HTTP. Cela ne prouve pas que l’acteur possède la réservation visée ni que la transition est permise. Placez les contrôles d’identité, d’objet et d’état avant la mutation. Un test doit essayer une méthode refusée et un acteur interdit séparément : les deux échecs n’ont pas la même cause.
Understand this common mistake Une route POST accorde automatiquement les droits d’objet.
Le routage ne remplace pas l’autorisation.
Un paramètre reste une entrée à interpréter
Un identifiant fourni par le chemin sélectionne une ressource, mais n’établit pas le droit de la consulter. Une résolution automatique en argument de contrôleur ne change pas cette frontière. Vérifiez l’existence puis le périmètre autorisé selon le modèle choisi. L’application doit refuser une ressource tierce même si son identifiant est correctement formé et si l’objet a été chargé sans erreur.
Understand this common mistake Un ID provenant du routeur prouve la propriété de l’objet.
La sélection et les permissions sont distinctes.
Contraindre la forme avec un requirement
Un requirement limite les formes de valeurs que le routeur reconnaît pour un paramètre. Il ne vérifie pas tous les invariants du domaine ni les permissions. Un nombre conforme peut encore désigner une ressource absente ou interdite. Utilisez le routage pour exprimer son contrat de correspondance, puis gardez la validation et l’autorisation au niveau où leur sens est connu.
Understand this common mistake Une expression de route valide tout invariant métier.
Le format ne couvre pas toutes les règles du domaine.
Générer une URL depuis le contrat de route
Utiliser un nom de route découple les liens de la forme concrète du chemin. Le contexte de génération, notamment hôte et schéma pour une URL absolue, doit rester maîtrisé. Une valeur Host reçue librement ne doit pas devenir une origine publique fiable sans politique adaptée. Les tests doivent vérifier le lien rendu dans le contexte réellement utilisé, pas seulement l’existence d’un nom dans la configuration.
Understand this common mistake Un Host client quelconque est toujours une origine publique fiable.
Le contexte doit être protégé des valeurs non admises.
La localisation fait partie du chemin
Des routes localisées peuvent produire des URL différentes pour une même fonction. Traduire seulement le libellé visible ne modifie pas automatiquement chaque chemin. Générez les liens selon le contexte de locale attendu et testez la navigation dans les deux interfaces. Un changement de langue doit conduire à la ressource prévue, sans perdre silencieusement l’identifiant ou une étape importante du parcours.
Understand this common mistake Traduire un libellé change automatiquement toutes les routes.
Les chemins doivent être définis et générés selon la configuration.
Existence et non-divulgation
Une ressource absente peut produire un 404 selon le contrat de l’API. Une ressource existante n’est pas pour autant lisible par tout acteur. La politique publique peut aussi limiter les différences qui divulgueraient son existence. Séparez ce que le client reçoit de ce que le diagnostic privé conserve. Dans tous les cas, le refus ne doit pas arriver après la distribution des données protégées.
Understand this common mistake Un objet existant doit être lisible par tous.
L’existence ne donne pas la permission.
Comprendre les collisions de correspondance
Deux routes de formes proches peuvent demander des contraintes ou une priorité pour exprimer l’intention. Un segment fixe comme examen ne doit pas être traité par erreur comme un nom de leçon si une route spécialisée est prévue. Testez les chemins limites et les méthodes. Une correspondance nominale ne démontre pas que toutes les routes voisines sont résolues selon le contrat voulu.
Understand this common mistake Toutes les routes de même forme sont nécessairement équivalentes.
L’ordre et les contraintes influencent la correspondance.
Le contrôleur construit un résultat observable
Le nom d’une action ne garantit ni son statut ni ses effets. Définissez la réponse attendue pour succès, entrée invalide, absence et refus de permission. Les composants peuvent intervenir avant ou après l’action ; le test doit donc observer le message final et le non-effet des chemins interdits. Un contenu HTML correct n’est pas une preuve suffisante d’une méthode ou d’une autorisation correctement contrôlée.
Understand this common mistake Le nom d’une action suffit à garantir son statut.
Le traitement doit établir la réponse.
Limiter les destinations de retour
Une URL de retour reçue d’un client ne doit pas permettre une redirection arbitraire vers un destinataire non admis. Définissez des destinations maîtrisées et comparez les composants avec un parseur adapté. Le texte visuel d’un domaine n’établit pas sa confiance. Si une redirection préserve méthode et corps, examinez aussi la diffusion de données sensibles avant de suivre le saut.
Understand this common mistake Toute URL de retour reçue est une destination autorisée.
Une entrée libre peut détourner la navigation.
Tester les méthodes non prises en charge
Une route correspondant au chemin peut refuser une méthode et produire un 405 selon la résolution du routeur. Ce cas ne se confond pas avec une ressource métier inexistante. Préparez les demandes GET, POST ou autres pertinentes et vérifiez le statut ainsi que l’absence d’effet. Une interface qui ne montre qu’un bouton ne limite pas les méthodes qu’un client direct peut essayer.
Understand this common mistake Tester un GET nominal prouve le refus de tout POST.
Chaque frontière pertinente demande une assertion.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
use Symfony\Component\Routing\Attribute\Route;
final class LabController {
#[Route('/lab/orders/{id}/cancel', methods: ['POST'])]
public function cancel(string $id): void {}
}
L’attribut de route de l’atelier déclare que l’action correspond à POST sur un chemin donné. L’identifiant reste une donnée de sélection fournie par l’appelant. Rien dans cet attribut n’établit que ce dernier possède la réservation, et l’action vide ne fournit pas une règle d’accès. Il faut donc ajouter l’autorisation au parcours effectif avant l’annulation.
Écrivez un test avec deux comptes et une réservation appartenant au premier. Le second doit être refusé même en utilisant exactement la méthode et la forme de route attendues. Ajoutez un test de méthode incorrecte pour montrer une autre frontière. Ces deux preuves empêchent de considérer qu’un routage strict remplace la connaissance des droits métier sur l’objet.
Worked example
<?php
use Symfony\Component\Routing\Attribute\Route;
final class DemoController {
#[Route('/reservations/{id}', name: 'demo_reservation', requirements: ['id' => '\d+'], methods: ['GET'])]
public function show(int $id): Symfony\Component\HttpFoundation\Response {
return new Symfony\Component\HttpFoundation\Response((string) $id);
}
}
La route limite ici id à des chiffres et l’accès à GET. Cet exemple de routage n’implémente ni recherche ni autorisation métier.
Practical exercise
Ajoutez une route de mutation et vérifiez que GET ne peut pas la déclencher.
View a suggested solution
Déclarez les méthodes appropriées, ajoutez le contrôle CSRF si le contexte le requiert et testez la réponse 405 pour les méthodes non autorisées. Générez les liens avec le nom de route.
- Tester correspondance et génération.
- Ne pas confondre existence et permission.
- Éviter les mutations via GET.
Knowledge check
Quel mécanisme limite un paramètre de route à une forme attendue ?
Explained answer: Un requirement — La contrainte participe à la correspondance de route.
Related lesson: Routes, contrôleurs et résolution d’arguments ↗
Reference documentation ↗ https://symfony.com/doc/8.0/routing.html
Module 4 / 8 · 90 min
Formulaires, contraintes et CSRF
Valider un modèle d’entrée avant de déclencher le métier.
Comprendre le mécanisme
Le composant Form transforme une soumission en données structurées. handleRequest prend en compte la requête ; isSubmitted et isValid répondent à des questions différentes. Un type de formulaire décrit les champs et leurs options. Les contraintes du Validator contrôlent les valeurs et groupes de validation. Une transformation convertit une représentation sans remplacer la validation du domaine obtenu.
Raisonner et vérifier
Un jeton CSRF protège le déclenchement d’une action dans un contexte de navigateur. Il n’authentifie pas une personne et n’autorise pas une ressource. Les uploads exigent aussi contrôle de contenu et destination. Pour une opération sensible, mappez le formulaire à un DTO dédié puis appelez le service métier seulement après validation et autorisation. Conservez les messages d’erreur utiles et testez les soumissions partielles ou mal formées.
Soumission et conformité sont deux états
Un formulaire peut avoir reçu une demande sans être conforme. isSubmitted et isValid répondent donc à des questions différentes. La persistance doit suivre le contrat prévu après ces vérifications, pas seulement le fait qu’un bouton a été activé. Les tests doivent envoyer une donnée invalide et contrôler l’absence d’écriture, même si le formulaire contient déjà un objet ou des valeurs récupérables par getData.
Understand this common mistake isSubmitted implique nécessairement isValid.
Soumission et conformité sont des états distincts.
Exprimer les invariants au bon niveau
Une contrainte de propriété peut vérifier un champ ; une cohérence entre deux champs d’un DTO peut demander une validation au niveau de la classe. Par exemple, une date de fin doit être comparée à la date de début selon le sens du parcours. Cette validation de données ne prouve pas que l’acteur a le droit de modifier l’objet. Maintenez une décision d’autorisation distincte avant l’effet.
Understand this common mistake Une contrainte remplace toute permission sur l’objet.
Le droit métier est une décision distincte.
Le jeton CSRF dépend du contexte
Un champ nommé token ne constitue pas une preuve si le serveur ne le vérifie pas selon l’usage et la session attendus. Le contrôle cible une demande forgée pouvant profiter de l’identité automatique du navigateur. Il ne remplace pas un voter sur une ressource. Testez valeur absente, incorrecte et valide, puis vérifiez séparément ce qu’un acteur connecté mais non autorisé peut encore demander.
Understand this common mistake Un champ nommé token valide automatiquement la demande.
La valeur doit être vérifiée selon son usage.
Limiter un fichier selon son usage
Une extension ou un Content-Type client ne suffit pas à établir le format, la sécurité ou le coût d’un fichier. Définissez taille, contenu accepté et traitement, puis gardez les limites amont et serveur cohérentes. Le stockage et la restitution doivent aussi éviter des chemins ou exécutions non maîtrisés. Un refus doit être testé sans laisser une importation partielle non prévue par le contrat.
Understand this common mistake Le suffixe du nom client garantit un upload sain.
La déclaration doit être confrontée au contenu et au contrat.
Transformer n’est pas valider tout le domaine
Un DataTransformer peut convertir une représentation en une autre et signaler un échec de conversion. Il ne garantit pas toutes les règles métier de l’objet obtenu. Une valeur convertie en nombre peut encore dépasser une borne ou être interdite dans l’état actuel. Distinguez grammaire d’entrée, représentation technique et invariants afin que chaque échec ait une responsabilité et un test explicites.
Understand this common mistake Toute chaîne reçue se transforme sans erreur en objet métier.
Les formats et valeurs peuvent être incompatibles.
Choisir les groupes selon le parcours
Les groupes de validation déterminent quelles contraintes s’appliquent à une situation. Un brouillon et une publication complète peuvent avoir des exigences différentes, mais cette flexibilité doit rester contrôlée. Vérifiez la sélection effective et les chemins qui pourraient contourner un groupe nécessaire. Un test nominal d’une seule étape ne prouve pas qu’un appel direct à une autre étape respecte les mêmes préconditions.
Understand this common mistake Tout groupe couvre automatiquement toutes les étapes métier.
Le choix de groupe détermine les contraintes actives.
Ne pas confondre visibilité de champ et admission
Un client peut construire sa propre demande et envoyer des valeurs absentes de l’interface affichée. Le serveur doit donc maîtriser les champs admis et l’usage qu’il en fait. Masquer un input ne protège pas une propriété sensible contre toute écriture réseau. Les tests doivent inclure des champs inattendus et vérifier que le modèle ne gagne pas un privilège ou un état impossible.
Understand this common mistake Masquer un input interdit toute modification réseau.
L’interface ne limite pas un client direct.
Contrôler le mapping des données
Le mapping du formulaire doit préserver les responsabilités et invariants de l’objet cible. Évitez de donner à une entrée libre l’accès à toutes les propriétés d’une entité, notamment rôles et propriétaire. Un DTO peut limiter ce contrat puis être traduit en opération métier autorisée. Le fait qu’un champ existe dans l’objet ne suffit pas à justifier qu’il soit modifiable dans ce parcours.
Understand this common mistake Tout nom reçu doit modifier librement toute propriété.
Le mapping doit préserver les invariants et droits.
Une erreur utile sans divulgation
Le message de validation doit aider à corriger l’entrée sans exposer de secret ou une trace interne. Les noms de propriétés techniques et valeurs sensibles peuvent demander une représentation publique différente. Conservez les diagnostics nécessaires dans un périmètre privé. Testez aussi les erreurs de transformation et exceptions inattendues : ces chemins ne doivent pas contourner la politique de présentation appliquée aux violations ordinaires.
Understand this common mistake Une violation doit afficher toutes les valeurs sensibles du compte.
Le diagnostic doit être minimisé.
Vérifier le refus et le non-effet
Une erreur affichée après une écriture ne fournit pas la propriété d’un rejet avant traitement. Pour chaque classe invalide, vérifiez réponse, état du stockage et appels aval pertinents. Gardez un cas valide qui confirme que le formulaire peut réellement aboutir. Cette matrice rend visibles les responsabilités séparées de soumission, conversion, validation, CSRF et autorisation, au lieu de les résumer au seul état visuel du bouton.
Understand this common mistake Un formulaire vert suffit à prouver tous les refus.
Les chemins négatifs vérifient la frontière.
Atelier guidé : lire, expliquer et vérifier le code
// Extrait après handleRequest($request) :
if ($form->isSubmitted()) {
$repository->save($form->getData());
}
L’extrait de l’atelier utilise seulement isSubmitted avant de récupérer les données et de demander leur persistance. Il peut donc ignorer des violations présentes sur un formulaire effectivement soumis. getData n’est pas une preuve générale de conformité et le nom save ne promet ni validation CSRF ni autorisation sur l’objet.
Construisez une demande de laboratoire avec un champ invalide puis observez l’appel au repository au moyen d’un double de test. La version correcte doit empêcher cet appel et présenter l’erreur prévue. Ajoutez ensuite un cas de cohérence entre deux champs et un acteur interdit sur l’objet. Vous vérifierez ainsi plusieurs frontières indépendantes, plutôt que donner au seul signal de soumission la responsabilité de toutes les règles du parcours.
Worked example
<?php
// Fragment dans un contrôleur disposant de $form et $request.
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$command = $form->getData();
// Autoriser puis transmettre la commande au service applicatif.
}
La condition évite d’exécuter le métier lors du premier affichage ou d’une soumission invalide. La décision d’autorisation reste explicite.
Practical exercise
Créez un DTO de réservation avec quantité positive, email valide et une contrainte entre deux dates.
View a suggested solution
Associez les contraintes simples aux champs et une validation de cohérence au DTO. Testez formulaire non soumis, données invalides, jeton absent et chemin autorisé.
- Séparer transformation et validation.
- Contrôler soumission et validité.
- Tester CSRF indépendamment des rôles.
Knowledge check
Quelle condition doit précéder le traitement d’un formulaire web valide ?
Explained answer: isSubmitted et isValid — Il faut une soumission effectivement validée.
Related lesson: Formulaires, contraintes et CSRF ↗
Reference documentation ↗ https://symfony.com/doc/8.0/forms.html
Module 5 / 8 · 90 min
Authentification, autorisation et voters
Appliquer une décision d’accès compréhensible et testable.
Comprendre le mécanisme
L’authentification établit un contexte d’identité ; l’autorisation décide ce qui est permis. Un firewall définit un périmètre et ses mécanismes. Un provider retrouve les utilisateurs ; un authenticator construit un parcours d’authentification avec les éléments de validation nécessaires. Les rôles et règles access_control sont utiles pour des politiques générales, tandis qu’un voter exprime une décision sur un attribut et un sujet métier.
Raisonner et vérifier
L’ordre des règles de contrôle peut modifier la règle appliquée : testez les URL publiques et protégées. Ne déduisez pas la propriété d’une ressource depuis un identifiant fourni par le client. Une interface masquant un bouton n’est pas une protection serveur. Les mots de passe sont hachés via PasswordHasher et les refus doivent être vérifiés dans les tests fonctionnels.
Identité et droit
L’authentification établit une identité selon un mécanisme. L’autorisation demeure une décision distincte. Un compte authentifié peut lire ses factures sans pouvoir lire celles du voisin. Séparez le mécanisme de connexion du contrôle de propriété et vérifiez les deux décisions dans les tests HTTP.
Understand this common mistake Toute identité connectée possède tous les rôles.
L’autorisation demeure une décision distincte.
Autoriser un sujet
Un voter peut exprimer un droit sur un sujet. Le code doit appliquer les conditions correctes. Un voter reçoit une opération, par exemple EDIT, et un document. Il compare propriétaire, tenant et état métier avec les droits de l’acteur ; denyAccessUnlessGranted doit intervenir avant toute modification.
Understand this common mistake Le nom voter donne automatiquement une bonne règle métier.
Le code doit appliquer les conditions correctes.
Premier access_control applicable
Les règles access_control suivent un ordre pertinent. La sélection configurée doit être comprise et testée. Placez une exception publique étroite avant une règle générale protégée lorsque le contrat le demande. Testez les chemins réellement résolus ; une règle générale placée trop tôt peut masquer toutes les suivantes.
Understand this common mistake Toute règle correspondante est forcément combinée sans priorité.
La sélection configurée doit être comprise et testée.
Coût du hachage
Les mots de passe demandent un hasher adapté. Le coût des essais hors ligne doit être pris en compte. Utilisez le password hasher du framework et sa vérification, plutôt qu’une comparaison de condensats maison. Prévoyez migration progressive et budget CPU pour les connexions simultanées, sans journaliser les mots de passe.
Understand this common mistake MD5 direct constitue toujours un hasher de mot de passe sûr.
Le coût des essais hors ligne doit être pris en compte.
Frontières des firewalls
Les firewalls ont des contextes et configurations. Le partage dépend des options et du modèle choisi. Une API stateless et une interface avec session peuvent avoir des mécanismes distincts. Vérifiez quel firewall correspond au chemin et comment son contexte traite la déconnexion et le rechargement de l’utilisateur.
Understand this common mistake Déclarer plusieurs firewalls fusionne toujours toutes les sessions.
Le partage dépend des options et du modèle choisi.
Session et intention
Le CSRF complète la sécurité de session. Le navigateur peut envoyer automatiquement son identité. Un formulaire qui change un IBAN doit vérifier son jeton CSRF même lorsque la session identifie correctement le compte. Le navigateur peut envoyer le cookie lors d’une demande provoquée ailleurs.
Understand this common mistake Un login réussi élimine tout risque CSRF.
Le navigateur peut envoyer automatiquement son identité.
Retirer un accès
Une désactivation doit être prise en compte selon le cycle choisi. Le modèle doit définir et appliquer le retrait d’accès. Définissez le délai de prise en compte d’un compte suspendu. Une session persistante et un jeton autonome n’offrent pas spontanément le même délai ; testez une demande après suspension avec des credentials déjà émis.
Understand this common mistake Un compte désactivé reste nécessairement admis jusqu’à tout logout manuel.
Le modèle doit définir et appliquer le retrait d’accès.
Contrôler avant agir
Le refus doit précéder les mutations sensibles. Le contrôle tardif peut laisser un effet interdit. Un repository save suivi d’un refus laisse potentiellement une écriture durable. Vérifiez dans le test que la demande interdite ne modifie aucune ligne et n’envoie aucun message ou appel externe.
Understand this common mistake Un accès refusé après écriture protège l’état métier.
Le contrôle tardif peut laisser un effet interdit.
Tester l’isolation
Une règle d’objet doit être testée avec deux acteurs. Le cas tiers vérifie une frontière supplémentaire. Créez Alice, Bob et un document appartenant à Alice. Bob doit échouer même avec un identifiant valide, tandis qu’Alice réussit : ce cas évite de confondre objet absent et objet interdit.
Understand this common mistake Un seul compte connecté prouve l’isolation entre propriétaires.
Le cas tiers vérifie une frontière supplémentaire.
Diagnostics protégés
Les credentials doivent rester hors des logs ordinaires. Les outils de diagnostic demandent un accès protégé. Le profiler et les traces peuvent contenir des cookies ou en-têtes sensibles. Contrôlez leur exposition en production et remplacez les valeurs secrètes par des événements de sécurité minimisés et corrélables.
Understand this common mistake Le profiler public peut exposer les secrets sans risque.
Les outils de diagnostic demandent un accès protégé.
Atelier guidé : lire, expliquer et vérifier le code
// Extrait de contrôleur ; document trouvé par identifiant :
$document = $repository->find($id);
return $this->render('document.html.twig', ['document' => $document]);
L’extrait charge un document puis le rend immédiatement. Le repository démontre seulement son existence. Introduisez une décision d’autorisation sur ce document pour l’acteur courant, puis testez avec un propriétaire et un tiers. Vérifiez aussi que la réponse d’erreur ne contient ni titre confidentiel ni contenu partiel du document.
L’existence de l’objet et la connexion ne suffisent pas à donner l’accès. La présentation ne décide pas du droit. Un encodage n’établit pas la propriété. Le nom de l’API ne vérifie pas les permissions.
Travail pratique : Écrivez la matrice d’autorisation propriétaire, autre membre et administrateur pour lire et annuler.
Critères de correction : Définissez attributs et sujet, implémentez le voter puis testez tous les couples acteur/action. Vérifiez qu’un refus n’appelle pas le service de mutation.
Worked example
<?php
// Fragment dans un contrôleur héritant d’AbstractController.
$this->denyAccessUnlessGranted('RESERVATION_CANCEL', $reservation);
$service->cancel($reservation);
L’autorisation est évaluée avant l’effet de bord. Le voter correspondant doit distinguer les acteurs et les états de réservation permis.
Practical exercise
Écrivez la matrice d’autorisation propriétaire, autre membre et administrateur pour lire et annuler.
View a suggested solution
Définissez attributs et sujet, implémentez le voter puis testez tous les couples acteur/action. Vérifiez qu’un refus n’appelle pas le service de mutation.
- Autoriser avant l’effet de bord.
- Vérifier l’ordre des règles générales.
- Tester la permission sur l’objet concret.
Knowledge check
Quel outil exprime une permission dépendant d’une réservation précise ?
Explained answer: Un voter recevant le sujet — Le voter peut appliquer la règle sur l’acteur et la ressource.
Related lesson: Authentification, autorisation et voters ↗
Reference documentation ↗ https://symfony.com/doc/8.0/security.html
Module 6 / 8 · 90 min
Twig, traduction et sérialisation de présentation
Rendre des données sans confondre texte, code et contexte.
Comprendre le mécanisme
Twig sépare la présentation et la logique applicative. Les blocs composent un héritage de templates ; include réutilise une partie de rendu. L’échappement automatique évite d’interpréter comme HTML une donnée rendue dans le contexte concerné. raw désactive cette protection : sa présence doit correspondre à un contenu réellement maîtrisé. Un filtre ne fournit pas une protection universelle pour tous les contextes JavaScript ou URL.
Raisonner et vérifier
Les clés de traduction sont résolues selon domaine et locale, avec repli prévu. Les messages comportant des nombres peuvent nécessiter un format de pluralisation adapté. Conservez les textes traduisibles séparés des identifiants métier. Pour une sortie JSON, utilisez les outils de sérialisation adaptés, groupes et normalisation au besoin ; évitez de rendre toutes les propriétés d’un objet sensible par commodité.
Contexte de sortie
L’échappement automatique dépend du contexte Twig configuré. Les sorties HTML, JavaScript et URL ont des contraintes différentes. Le texte HTML, un attribut, une URL et du JavaScript ne suivent pas les mêmes règles. Conservez les données dans le contexte prévu et vérifiez le DOM produit avec une valeur contenant guillemets et chevrons.
Understand this common mistake Toute variable Twig est sûre dans tout contexte possible.
Les sorties HTML, JavaScript et URL ont des contraintes différentes.
Retrait par raw
raw retire une protection d’échappement. La donnée doit déjà suivre une politique de contenu adaptée. Afficher une biographie contrôlée par un visiteur avec raw transforme du balisage en contenu actif. Si du HTML est nécessaire, appliquez une politique de nettoyage explicite adaptée à cet usage avant affichage.
Understand this common mistake raw rend automatiquement fiable tout contenu utilisateur.
La donnée doit déjà suivre une politique de contenu adaptée.
Domaine et locale
Les traductions ont un domaine et une locale. La présentation n’accorde pas les droits. Une clé peut exister dans messages mais manquer dans validators. Vérifiez le domaine demandé et le repli linguistique au lieu d’ajouter la même clé dans tous les catalogues sans comprendre la résolution.
Understand this common mistake Un texte traduit décide des permissions serveur.
La présentation n’accorde pas les droits.
Paramètres et messages
Les paramètres de traduction peuvent porter des données. Le contexte de rendu doit garder la séparation données/code. Un prénom injecté dans une traduction reste une donnée. Ne construisez pas une chaîne HTML de confiance par concaténation puis raw ; vérifiez les paramètres et les choix de pluralisation du moteur utilisé.
Understand this common mistake Toute valeur interpolée devient du code fiable.
Le contexte de rendu doit garder la séparation données/code.
Clés absentes
Une clé manquante doit être détectée par les vérifications. Il peut s’agir d’un repli sans message disponible. Une interface peut rester techniquement valide en affichant sa clé de traduction. Ajoutez des assertions sur les libellés importants et le repli attendu afin de détecter les erreurs visibles sans exception PHP.
Understand this common mistake Une page affichant une clé prouve une traduction complète.
Il peut s’agir d’un repli sans message disponible.
Héritage et blocs
Une extension de template peut séparer des blocs. Le mécanisme permet de spécialiser certains blocs. Un template enfant remplace des blocs du parent. Gardez une structure commune pour navigation et sécurité des attributs ; vérifiez qu’une surcharge ne retire pas silencieusement un formulaire ou une information obligatoire.
Understand this common mistake Tout template enfant doit dupliquer intégralement le parent.
Le mécanisme permet de spécialiser certains blocs.
Macros explicites
Les macros ont une portée et des arguments explicites. Les dépendances du rendu doivent être passées correctement. Passez le sujet et la locale nécessaires comme arguments de macro. Cette signature rend les dépendances visibles et évite qu’une réutilisation dans un autre contexte dépende accidentellement de variables disponibles ailleurs.
Understand this common mistake Une macro voit nécessairement tout état local de l’appelant.
Les dépendances du rendu doivent être passées correctement.
Liens et destination
Une URL doit être générée et validée selon son usage. Le contenu de destination demande aussi une politique. Générez les chemins internes par nom de route. Une destination externe issue d’une entrée doit encore être admise selon sa politique ; l’échappement HTML d’un lien ne rend pas son schéma acceptable.
Understand this common mistake Échapper une URL suffit à autoriser tout schéma.
Le contenu de destination demande aussi une politique.
Langue annoncée
Un contenu de cours français peut avoir une interface anglaise annoncée. Le contenu et les messages d’interface ont des sources différentes. Une interface anglaise peut présenter un cours français si ce choix est indiqué. Testez navigation, attribut lang et liens localisés, afin que l’apprenant connaisse la langue du contenu avant de commencer.
Understand this common mistake Changer la locale traduit automatiquement tout texte stocké en JSON.
Le contenu et les messages d’interface ont des sources différentes.
Contrat de présentation
Les tests doivent vérifier le texte et les attributs rendus. La syntaxe ne couvre pas l’ensemble de l’interface. Pour une API utilisateur, un DTO ou des groupes de sérialisation exposent les champs utiles. Vérifiez que hash de mot de passe, token interne et données de tiers n’apparaissent jamais dans le contrat rendu.
Understand this common mistake Un lint Twig prouve tout comportement interactif navigateur.
La syntaxe ne couvre pas l’ensemble de l’interface.
Atelier guidé : lire, expliquer et vérifier le code
<article>
{{ body|raw }}
</article>
L’atelier retire l’échappement d’une valeur potentiellement contrôlée par un utilisateur. Injectez un exemple de balisage inoffensif dans un test local et observez le DOM. La correction doit afficher le texte attendu ou un HTML nettoyé selon une politique explicite. Contrôlez également les attributs et la sérialisation, pas seulement le texte visible.
Le filtre n’est pas un assainisseur universel. raw retire une protection au lieu d’ajouter une insertion textuelle. Le filtre de rendu ne décide pas du droit d’accès. Un contenu actif peut être interprété si aucune politique adaptée n’a été appliquée.
Travail pratique : Ajoutez un libellé contenant <script> puis vérifiez sa représentation ; rendez aussi une page en deux locales.
Critères de correction : Vérifiez le texte obtenu et l’absence de balise interprétée. Testez les liens localisés et le repli d’une clé. N’utilisez pas raw pour faire disparaître un problème de format.
Worked example
{# Template Twig de laboratoire #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>{{ reservation.label }}</h1>
<a href="{{ path('app_training') }}">{{ 'catalogue.title'|trans({}, 'training') }}</a>
{% endblock %}
Le libellé est échappé lors du rendu HTML. Le lien est généré par le routeur et le texte de navigation par le traducteur.
Practical exercise
Ajoutez un libellé contenant <script> puis vérifiez sa représentation ; rendez aussi une page en deux locales.
View a suggested solution
Vérifiez le texte obtenu et l’absence de balise interprétée. Testez les liens localisés et le repli d’une clé. N’utilisez pas raw pour faire disparaître un problème de format.
- Éviter raw sur une entrée utilisateur.
- Tester domaines et locales.
- Limiter explicitement les données sérialisées.
Knowledge check
Quel filtre désactive l’échappement Twig et exige une confiance particulière ?
Explained answer: raw — raw marque le rendu comme sûr pour l’échappement concerné.
Related lesson: Twig, traduction et sérialisation de présentation ↗
Reference documentation ↗ https://symfony.com/doc/8.0/templates.html
Module 7 / 8 · 90 min
Cache HTTP, cache applicatif et Messenger
Séparer fraîcheur, confidentialité et reprise des traitements.
Comprendre le mécanisme
Le cache HTTP s’appuie sur les en-têtes de réponse : expiration pour la fraîcheur, validateurs comme ETag pour la revalidation. Une réponse personnelle ne doit pas devenir publique par erreur. Un cache applicatif mémorise un calcul selon une clé et une stratégie d’invalidation ; une clé omettant un tenant ou une locale peut mélanger les résultats. Une durée arbitraire n’est pas une preuve de cohérence.
Raisonner et vérifier
Messenger sépare émission et traitement de messages ; un transport asynchrone implique des workers et une politique d’échec. Une livraison peut être répétée, notamment après un incident. Rendez le handler idempotent avec une clé métier stable, et observez les erreurs définitives. Ne confondez pas l’acceptation d’un message avec la réussite de tous ses effets.
Clé et portée
Un cache doit définir ses clés et sa portée. La personnalisation doit entrer dans le contrat de sélection. Un tableau de bord personnel ne doit pas être stocké comme une réponse publique partagée. Une clé de calcul dépendant du tenant doit inclure ce tenant, ainsi que les autres dimensions réellement variables.
Understand this common mistake Un cache partagé isole automatiquement tous les utilisateurs.
La personnalisation doit entrer dans le contrat de sélection.
Invalidation cohérente
Une invalidation doit accompagner les changements pertinents. La propagation doit être conçue. Après modification d’un prix, invalidez les entrées qui en dépendent ou utilisez une version de clé. Un TTL borne parfois l’obsolescence mais ne fournit pas une mise à jour immédiate garantie.
Understand this common mistake Écrire en base invalide automatiquement tout cache applicatif.
La propagation doit être conçue.
Synchrone ou asynchrone
Messenger peut traiter un message de manière asynchrone. La fin dépend du transport et du handler. Le routage Messenger détermine si un message est envoyé vers un transport ou traité dans le processus. Vérifiez la configuration effective ; le seul appel dispatch ne garantit pas un traitement différé.
Understand this common mistake Dispatch signifie toujours effet métier terminé.
La fin dépend du transport et du handler.
Livraison répétée
Une livraison répétée demande de l’idempotence. Les reprises peuvent répéter le traitement. Après effet durable et perte d’acquittement, le message peut revenir. Utilisez un identifiant métier unique et une déduplication durable dans la même transaction que l’effet local, puis testez deux livraisons successives.
Understand this common mistake Un message ne peut jamais être livré plus d’une fois.
Les reprises peuvent répéter le traitement.
Frontière transactionnelle
Un handler doit gérer ses limites transactionnelles. Les systèmes ont des frontières de transaction distinctes. Une transaction SQL ne peut pas annuler un paiement déjà accepté par un prestataire. Modélisez les états et la reprise ; un outbox transactionnel traite le lien entre écriture locale et émission différée.
Understand this common mistake Toute file participe automatiquement au commit SQL local.
Les systèmes ont des frontières de transaction distinctes.
Reprises bornées
Une stratégie de retry doit être bornée. Il peut amplifier une panne et bloquer la file. Distinguez erreur transitoire et message invalide. Configurez délai, plafond et jitter appropriés ; une reprise illimitée d’une opération non idempotente peut multiplier les effets au lieu de restaurer le service.
Understand this common mistake Un retry infini améliore toujours la disponibilité.
Il peut amplifier une panne et bloquer la file.
File d’échec
Une file d’échec demande une procédure d’exploitation. Le classement ne remplace pas l’investigation et la reprise. Documentez qui examine un message en échec, quelles données sont sensibles et comment le rejouer. La correction doit préserver l’identité métier pour éviter de transformer une intervention d’exploitation en nouvelle opération.
Understand this common mistake Déplacer un message en échec corrige son défaut métier.
Le classement ne remplace pas l’investigation et la reprise.
Workers et release
Les workers doivent suivre le cycle de release. Un processus peut conserver son code et état jusqu’à redémarrage. Un processus long peut conserver ancien code ou références d’objets. Organisez son arrêt contrôlé et son redémarrage autour de la release, en respectant les messages en cours et leur possibilité de relivraison.
Understand this common mistake Un worker ancien utilise automatiquement tout nouveau code chargé sur disque.
Un processus peut conserver son code et état jusqu’à redémarrage.
Payload minimal
Les payloads doivent éviter les secrets inutiles. Le stockage et les accès demandent une minimisation. Envoyez les identifiants et données nécessaires au contrat, sans cookies ni mots de passe. La queue, ses sauvegardes et ses interfaces d’administration deviennent elles aussi des surfaces de conservation des données.
Understand this common mistake Toute file interne peut conserver indéfiniment les mots de passe.
Le stockage et les accès demandent une minimisation.
Panne après effet
Les tests doivent simuler une réponse perdue après effet. La répétition doit être testée explicitement. Un test qui échoue avant toute écriture ne couvre pas le doublon critique. Simulez un effet réussi suivi d’une erreur d’acquittement et confirmez que la reprise conserve un seul résultat métier.
Understand this common mistake Un seul dispatch nominal prouve la déduplication.
La répétition doit être testée explicitement.
Atelier guidé : lire, expliquer et vérifier le code
// handle(PaymentCaptured $message)
$ledger->append($message->paymentId, $message->amount);
$email->sendReceipt($message->paymentId);
// aucune déduplication n’est définie dans l’exercice
Le handler d’exemple applique un effet avant l’acquittement. Rejouez le même message dans une base de test, puis comptez les écritures et les appels externes. La correction doit utiliser une identité d’opération stable et définir sa transaction. Pour une frontière externe, ajoutez un état durable permettant de réconcilier une réponse perdue.
Cela ajoute un effet sans résoudre le doublon. Retirer la trace ne protège pas les données. Les reprises peuvent répéter des effets déjà appliqués. Le type ne donne pas l’unicité d’exécution.
Travail pratique : Définissez clé et invalidation d’un cache de catalogue, puis concevez un handler supportant la répétition d’un message.
Critères de correction : Incluez les dimensions du résultat dans la clé et invalidez lors des changements pertinents. Pour le handler, enregistrez la clé métier traitée avec l’effet durable et testez une double livraison.
Outbox : rendre durable l’intention de publier
Écrire l’état SQL puis publier un événement crée une fenêtre : le commit peut réussir et la publication échouer. Publier avant commit inverse le risque, car un événement peut annoncer un état finalement annulé.
Écrivez changement d’état et ligne d’outbox dans la même transaction locale. Après commit, un relais lit cette intention durable et publie avec reprise. Cette stratégie ferme la perte de l’intention entre les deux écritures, sans fournir une transaction distribuée universelle entre base et broker.
Le relais peut publier puis perdre son acquittement, donc republier. Le consommateur doit être idempotent avec une identité d’événement et une transition durable cohérente. Testez panne après commit avant publication, puis panne après publication avant marquage de la ligne. Vérifiez qu’aucun événement n’est émis pour une transaction annulée et qu’une relivraison ne répète pas l’effet métier. Surveillez également retard et lignes bloquées pour rendre la reprise opérable.
Points de vigilance : La publication pourrait annoncer une capture locale ensuite annulée. L’intention durable est atomique avec l’état ; le relais peut republier, d’où l’idempotence en aval. Le processus peut mourir après le commit et avant le callback ou pendant la publication. Une réponse HTTP et un accusé du broker appartiennent à des échanges distincts.
Reference documentation ↗
Port de paiement : représenter attente et action requise
Un booléen success fusionne des états qui demandent des comportements différents. Un moyen de paiement peut nécessiter une action client, rester en traitement, être refusé ou atteindre un succès confirmé. Ramener attente à false encourage parfois une nouvelle tentative alors qu’un effet est déjà en cours.
Définissez un résultat typé avec état, référence prestataire et données nécessaires à l’action admise. Documentez transitions et acteurs qui peuvent les déclencher. Un adaptateur traduit les capacités du partenaire sans masquer la différence entre autorisé, capturé et encore en attente lorsque le cas d’usage en dépend.
Testez action requise puis reprise, notification répétée et événement arrivant hors ordre. La transition doit préserver les invariants et éviter de revenir d’un état final vers un état ancien par simple copie de webhook. L’interface doit afficher une attente honnête et retrouver l’état serveur, plutôt que considérer le callback navigateur comme une preuve définitive.
Points de vigilance : Cette réduction perd l’attente et peut déclencher une relance dangereuse. Le succès du transport ne prouve pas celui du paiement. Une opération longue ou asynchrone ne doit pas immobiliser une transaction SQL en attendant son résultat. Le contrat représente les états nécessaires au cas d’usage ; adapter les capacités propres aux partenaires sans les masquer.
Reference documentation ↗
Worked example
<?php
use Symfony\Component\HttpFoundation\Response;
$response = new Response('contenu public identique pour tous');
$response->setPublic();
$response->setMaxAge(60);
Ce fragment suppose un contenu réellement public et identique pour les utilisateurs concernés. Il serait inadapté à un tableau de bord personnel.
Practical exercise
Définissez clé et invalidation d’un cache de catalogue, puis concevez un handler supportant la répétition d’un message.
View a suggested solution
Incluez les dimensions du résultat dans la clé et invalidez lors des changements pertinents. Pour le handler, enregistrez la clé métier traitée avec l’effet durable et testez une double livraison.
- Ne pas partager une réponse privée.
- Inclure les dimensions utiles dans la clé.
- Traiter les répétitions et les échecs.
Knowledge check
Un tableau de bord personnel peut-il être marqué public sans autre précaution ?
Explained answer: Non, cela peut exposer son contenu à un cache partagé — La confidentialité fait partie de la politique de cache.
Related lesson: Cache HTTP, cache applicatif et Messenger ↗
Reference documentation ↗ https://symfony.com/doc/8.0/http_cache.html
Module 8 / 8 · 90 min
Tests, console, événements et composants utiles
Vérifier les contrats du framework et préparer une exécution observable.
Comprendre le mécanisme
Un test unitaire isole une règle ; un KernelTestCase permet de travailler avec le conteneur ; un WebTestCase exerce le parcours HTTP avec un client de test. Les assertions doivent porter sur des effets observables, y compris les refus et les erreurs. Le profiler et la barre de debug servent au diagnostic de développement, pas à l’exposition publique des secrets. Les commandes Console ont des arguments, options, codes de sortie et niveaux de verbosité.
Raisonner et vérifier
Pour les composants annexes, conservez le même raisonnement de contrat : Clock facilite un temps contrôlable, EventDispatcher découple la notification, Filesystem et Finder manipulent les fichiers, Process exécute un processus avec arguments explicites. Mailer/Mime construisent et envoient des messages ; leur transport doit être substituable en test. Le Serializer exige un périmètre de données choisi. Testez échec de commande, environnement et absence de réseau imposé.
Isoler un contrat
Un test unitaire cible un contrat isolé. Son périmètre est plus limité. Un test unitaire d’une règle de remise peut injecter catalogue et horloge. Contrôlez entrées, résultat et appels significatifs ; un test qui copie chaque ligne de l’implémentation protège mal le comportement attendu.
Understand this common mistake Un test unitaire prouve tout déploiement réseau.
Son périmètre est plus limité.
Parcours HTTP
Un test fonctionnel vérifie un parcours applicatif. Les données et environnements doivent être isolés. WebTestCase fournit un client de test pour exercer le kernel. Vérifiez statut, contenu et absence d’effet interdit ; remplacez seulement les frontières réellement externes, sans contourner le contrôle d’accès que vous voulez mesurer.
Understand this common mistake Tout test fonctionnel utilise nécessairement la base de production.
Les données et environnements doivent être isolés.
Code de sortie
Une commande doit retourner un code de sortie adapté. Le processus appelant utilise aussi le code de retour. Une commande de migration partielle doit retourner un échec non nul lorsqu’elle n’a pas satisfait son contrat. Les scripts CI utilisent ce signal ; une ligne rouge imprimée avec code zéro peut laisser continuer la release.
Understand this common mistake Afficher Erreur en texte suffit à faire échouer toute CI.
Le processus appelant utilise aussi le code de retour.
Entrées de commande
Les entrées de commande doivent être validées. Un opérateur ou automate peut fournir une valeur incorrecte. Validez nom de fichier, identifiant et options avant utilisation. Un appel CLI interne reste exposé aux erreurs de l’opérateur et à une donnée provenant d’un autre système ; un prompt interactif n’est pas une validation.
Understand this common mistake Une option CLI est forcément de confiance.
Un opérateur ou automate peut fournir une valeur incorrecte.
Sorties minimisées
Les secrets ne doivent pas être imprimés par défaut. Le diagnostic doit minimiser les capacités sensibles. Évitez de copier une URL de connexion contenant un mot de passe dans les logs CI. Préférez un diagnostic de composant et un identifiant de corrélation, avec détails protégés dans un canal approprié.
Understand this common mistake Toute console doit afficher les mots de passe saisis.
Le diagnostic doit minimiser les capacités sensibles.
Fixtures cohérentes
Les fixtures doivent respecter les invariants. Le scénario doit préparer ses préconditions. Une fixture de réservation doit respecter relations, contraintes et états possibles. Si elle contourne ces règles, un test vert peut décrire une situation que la production ne peut jamais atteindre.
Understand this common mistake Une base vide implique que tous les tests passent.
Le scénario doit préparer ses préconditions.
Horloge contrôlable
Une horloge injectée rend les délais testables. La dépendance temporelle peut être contrôlée. Injectez une horloge pour comparer maintenant à expiresAt. Testez juste avant, à la borne et après l’expiration ; le choix inclusif ou exclusif doit être explicite et reproductible sans attente réelle.
Understand this common mistake Chaque test d’expiration doit attendre réellement une heure.
La dépendance temporelle peut être contrôlée.
Refus de méthode
Les méthodes refusées méritent un test. Les chemins d’accès peuvent différer. Un endpoint prévu en POST doit être essayé en GET. Vérifiez le rejet et l’absence de mutation, sans réduire le test à la présence d’un bouton qui n’envoie normalement que la bonne méthode.
Understand this common mistake Un GET réussi prouve l’absence de mutations via POST.
Les chemins d’accès peuvent différer.
Cycle de kernel
Le kernel et les services ont un cycle à respecter. Les fuites d’état rendent les résultats dépendants de l’ordre. Le client de test peut redémarrer le kernel entre demandes. Ne supposez pas qu’une instance récupérée avant la première requête sera nécessairement celle utilisée ensuite ; configurez les doubles au bon moment.
Understand this common mistake Deux scénarios peuvent partager tout état mutable sans isolation.
Les fuites d’état rendent les résultats dépendants de l’ordre.
Portée des preuves
Une suite verte ne certifie pas toute sécurité réelle. Les garanties dépendent des cas et hypothèses couverts. Une suite ne couvre que les cas et hypothèses exécutés. Ajoutez revues, analyse des dépendances et contrôles d’exploitation ; consignez version et environnement pour savoir à quelle release le résultat s’applique.
Understand this common mistake Un résultat de tests remplace tout examen du périmètre.
Les garanties dépendent des cas et hypothèses couverts.
Atelier guidé : lire, expliquer et vérifier le code
// Après détection d’un échec de sauvegarde :
$output->writeln('Backup failed');
return Command::SUCCESS;
La commande illustrée affiche un message d’échec tout en pouvant signaler un succès au système appelant. Exécutez-la avec CommandTester et vérifiez ensemble contenu utile et code de sortie. Ajoutez un cas d’entrée invalide et un cas nominal. L’objectif est un contrat exploitable par une personne comme par un script.
Les commandes ont précisément un statut de sortie. Une CI peut se fier au code de sortie plutôt qu’au texte. L’affichage ne change pas le code retourné. La constante ne restaure pas les données.
Travail pratique : Écrivez une commande de vérification qui retourne un code non nul quand une dépendance indispensable manque.
Critères de correction : Injectez une abstraction de vérification, testez succès et échec avec CommandTester et vérifiez le code de sortie. Pour HTTP, testez un parcours complet et un refus d’accès sans base de production.
Worked example
// Extrait d’un test WebTestCase
$client = static::createClient();
$client->request("GET", "/formations");
self::assertResponseIsSuccessful();
self::assertSelectorExists("h1");
Le test observe une réponse et son contenu. Complétez-le avec les assertions de langue, accès interdit et données invalides correspondant au contrat réel.
Practical exercise
Écrivez une commande de vérification qui retourne un code non nul quand une dépendance indispensable manque.
View a suggested solution
Injectez une abstraction de vérification, testez succès et échec avec CommandTester et vérifiez le code de sortie. Pour HTTP, testez un parcours complet et un refus d’accès sans base de production.
- Tester les codes de sortie.
- Substituer temps et transports.
- Ne jamais tester sur des données de production.
Knowledge check
Quel type de test convient à un parcours HTTP Symfony ?
Explained answer: WebTestCase avec un client de test — Il exerce le kernel et permet des assertions sur la réponse.
Related lesson: Tests, console, événements et composants utiles ↗
Reference documentation ↗ https://symfony.com/doc/8.0/testing.html