Module 1 / 8 · 90 min
Types, tableaux, chaînes et comparaisons
Distinguer absence, valeur fausse, égalité stricte et conversion.
Comprendre le mécanisme
PHP associe un type à chaque valeur. Un tableau est une table ordonnée de clés et de valeurs, pas seulement une liste. Les clés numériques écrites comme chaînes peuvent être normalisées ; distinguez une liste JSON d’un objet. Une comparaison stricte vérifie valeur et type, alors qu’une comparaison faible peut convertir les opérandes. isset considère une valeur null comme absente ; array_key_exists vérifie la présence de la clé. Ces différences deviennent des décisions métier lorsqu’un champ peut être volontairement vide.
Raisonner et vérifier
Avant un calcul, définissez le contrat d’entrée : type attendu, borne et signification de null. Ne transformez pas automatiquement une entrée invalide en zéro. Les chaînes PHP contiennent des octets ; strlen ne mesure pas les caractères Unicode. Pour une recherche qui peut retourner zéro, comparez explicitement avec false. Les expressions régulières valident une forme, sans remplacer les règles métier.
Le contrat appartient aussi au site d’appel
Un type scalaire protège une frontière d’appel, mais son comportement dépend du contexte PHP. Pour un appel direct à une fonction utilisateur, strict_types est déterminé par le fichier appelant. Déclarer la fonction dans un fichier strict ne rend donc pas automatiquement strict un autre fichier qui l’appelle. Séparez cette règle de la validation d’une quantité : un entier négatif reste un entier parfaitement valide pour le langage.
Erreur fréquente à comprendre strict_types rend toute donnée réseau valide.
Le typage d’appel ne vérifie pas les invariants métier.
Présence, absence et null
Une donnée peut être absente, présente avec null ou présente avec une valeur vide. Ces trois états peuvent demander des décisions différentes dans une modification partielle de profil. array_key_exists détecte la clé même si sa valeur est null ; isset retourne alors false. Un paramètre nullable autorise null dans le contrat, sans décider si null signifie suppression, valeur inconnue ou erreur métier dans votre API.
Erreur fréquente à comprendre Une valeur null convient à tout paramètre int non nullable.
Le contrat doit annoncer cette possibilité.
Lire une union avant d’utiliser sa valeur
Une union représente plusieurs possibilités encore ouvertes. Avec un résultat entier ou false, commencez par distinguer explicitement false, puis utilisez l’entier restant. strpos peut retourner zéro pour une occurrence au début : une condition de vérité seule perd cette occurrence valide. Le principe est de réduire les possibilités par une garde qui correspond au contrat réel de la fonction, plutôt que de supposer une valeur favorable.
Erreur fréquente à comprendre Une union permet toute opération de n’importe quel membre.
L’opération doit convenir au type réellement présent.
Égalité de représentation et égalité de valeur
La comparaison faible peut rapprocher une chaîne numérique et un entier. La comparaison stricte exige aussi le même type : le texte « 42 » ne devient pas identique à l’entier 42. Choisissez la règle selon le domaine. Pour un identifiant reçu du réseau, validez sa grammaire et sa représentation avant comparaison ; ne laissez pas une coercition implicite décider si deux identités sont équivalentes.
Erreur fréquente à comprendre La comparaison stricte convertit toujours chaînes et nombres.
Elle évite cette coercition de comparaison.
Table associative ou liste ordonnée
Un tableau PHP organise des clés et des valeurs ; il ne promet pas des indices continus. Retirer un élément peut laisser un trou et influencer la sérialisation JSON. Avant de renvoyer une liste, vérifiez le contrat des indices et réindexez lorsque c’est réellement souhaité. Une table de configuration avec des clés métier doit, au contraire, conserver ces clés : réindexer aveuglément ferait perdre leur signification.
Erreur fréquente à comprendre Tout tableau PHP est nécessairement une liste continue.
Les clés peuvent être associatives ou discontinues.
Une conversion n’est pas une validation
Un cast réalise une transformation selon les règles du langage. Il ne prouve pas que l’entrée respecte la grammaire attendue par votre application. Transformer immédiatement une valeur en entier peut mélanger donnée incorrecte et zéro légitime. Un parseur robuste distingue d’abord absence et type, vérifie ensuite la forme admissible, puis applique les bornes. La réponse d’erreur doit conserver cette distinction au lieu de fabriquer une valeur apparemment valide.
Erreur fréquente à comprendre Tout cast conserve intégralement le sens métier.
La conversion doit suivre un contrat d’entrée.
Décimaux et exactitude monétaire
Un flottant binaire ne représente pas exactement tous les décimaux usuels. Un calcul affiché avec deux chiffres peut masquer une approximation interne. Pour un montant, choisissez des entiers dans une unité explicitement définie ou un outil décimal approprié. L’unité, les arrondis et les limites font partie du contrat d’échange ; le type float et un formatage d’affichage ne suffisent pas à établir l’exactitude du calcul.
Erreur fréquente à comprendre Un float garantit tous les calculs monétaires exacts.
Le domaine monétaire demande une représentation adaptée.
Initialiser avant de lire
Une propriété typée peut être dans un état non initialisé, distinct d’une valeur null autorisée. Son type ne lui donne pas automatiquement zéro ou une chaîne vide. Le constructeur doit établir les invariants nécessaires avant que les autres méthodes l’utilisent. Testez une construction valide et les chemins incomplets. Ajouter une valeur par défaut arbitraire peut seulement cacher qu’une information obligatoire n’a jamais été fournie.
Erreur fréquente à comprendre Toute propriété int possède automatiquement zéro après construction.
L’accès exige une initialisation conforme au contrat.
Une chaîne est une séquence d’octets
PHP peut conserver dans une string des octets qui ne forment aucun texte UTF-8 valide. strlen mesure cette longueur en octets, pas un nombre universel de caractères visibles. Pour une limite de fichier, cette mesure est pertinente ; pour un libellé international, définissez l’encodage et la notion de longueur attendue. Les validations de texte, le stockage et la présentation doivent employer le même contrat.
Erreur fréquente à comprendre Toute string PHP est automatiquement un texte UTF-8 valide.
La validité d’encodage doit être établie si elle est requise.
Du type à la permission
Recevoir un identifiant entier établit au mieux une propriété de représentation. Cela ne démontre pas que l’acteur peut lire la ligne correspondante. Après validation du format, résolvez la ressource dans le périmètre autorisé et vérifiez l’opération demandée. Un test complet essaie le même identifiant avec le propriétaire et avec un autre compte : les deux entrées sont bien typées, mais leurs permissions peuvent différer.
Erreur fréquente à comprendre Un identifiant int donne accès à toute ligne correspondante.
L’autorisation demeure une règle indépendante.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
$value = '42';
var_dump($value == 42, $value === 42);
Dans l’exercice guidé, value contient une chaîne dont les deux caractères représentent un nombre. La première comparaison utilise les règles de l’égalité faible et obtient true. La seconde ajoute l’exigence de type : string et int sont différents, donc elle obtient false. strict_types n’interdit pas ces opérateurs et ne transforme pas la variable avant comparaison.
Reproduisez le programme dans un fichier de laboratoire, puis remplacez successivement la valeur par l’entier 42, la chaîne vide et null. Notez séparément le type et les deux résultats. Terminez par un parseur qui accepte seulement la représentation décidée pour votre API. Cette dernière étape vérifie le contrat métier ; elle ne se déduit pas du simple succès d’une comparaison.
strict_types : examiner le fichier qui appelle
<?php // amount.php
declare(strict_types=1);
function amount(int $value): int { return $value; }
<?php // endpoint.php — fichier distinct, sans strict_types
require 'amount.php';
var_dump(amount('1500'));
Pour cet appel direct à une fonction utilisateur, le mode strict des paramètres scalaires dépend du fichier appelant. Déclarer la fonction dans un fichier strict ne rend pas automatiquement strict le fichier qui l’utilise. Dans le scénario posé, l’appelant non strict peut convertir la chaîne numérique '1500' en entier.
Créez deux fichiers appelants qui invoquent la même fonction typée int, l’un avec strict_types=1 et l’autre sans. Vérifiez le type reçu et le refus du chemin strict pour la chaîne. Cette comparaison isole la règle au lieu de changer simultanément fonction, valeur et contexte.
Le typage ne remplace pas une validation du montant. Un entier converti avec succès peut dépasser un plafond, porter la mauvaise unité ou viser une opération interdite. À une frontière JSON, définissez les représentations admises et validez-les avant d’appeler le domaine, afin que le contrat ne dépende pas accidentellement de la permissivité d’un fichier.
Points de vigilance : Pour cet appel direct et ces arguments scalaires, le mode du fichier appelant s’applique. La déclaration int est effective : la chaîne numérique est convertie ici. L’appelant non strict permet la conversion de cette chaîne numérique en int. Les fichiers peuvent avoir des modes différents ; les frontières doivent être maîtrisées.
Documentation de référence ↗
Exemple commenté
<?php
declare(strict_types=1);
$data = ['limit' => null];
var_dump(isset($data['limit']));
var_dump(array_key_exists('limit', $data));
var_dump(strpos('stock', 's') !== false);
Le programme affiche false, true, true. Une clé présente peut contenir null ; une occurrence à la position zéro est une occurrence valide.
Exercice de mise en pratique
Écrivez un parseur de quantité qui distingue champ absent, chaîne vide, entier zéro, nombre positif et texte non numérique.
Voir une proposition de correction
Validez d’abord la présence et le format, puis la borne strictement positive. Retournez une erreur structurée pour chaque classe invalide ; ajoutez les cas 0, "0", null et "12x" aux tests.
- Tester les bornes et les valeurs fausses.
- Distinguer octets et caractères.
- Employer === lorsque le type fait partie du contrat.
Contrôle de connaissances
Une clé existe et contient null. Quel test confirme sa présence ?
Réponse expliquée : array_key_exists — La présence est indépendante de la valeur null.
Cours associé : Types, tableaux, chaînes et comparaisons ↗
Documentation de référence ↗ https://www.php.net/manual/en/language.types.php
Module 2 / 8 · 90 min
Fonctions, portée, closures et générateurs
Expliquer passage par valeur, capture et évaluation paresseuse.
Comprendre le mécanisme
Une fonction possède une portée locale ; elle ne voit pas spontanément les variables du contexte appelant. Les paramètres sont passés par valeur sauf déclaration explicite par référence. Une closure peut capturer une valeur avec use ; la capture par référence partage au contraire la variable. Une fonction fléchée capture les variables utilisées par valeur. Les paramètres nommés rendent les noms de paramètres importants pour les appels ; une liste variadique recueille des arguments supplémentaires.
Raisonner et vérifier
Documentez les effets de bord et préférez des fonctions dont les dépendances sont explicites. Un générateur suspend son exécution à yield : créer le générateur ne parcourt pas déjà tout le fichier. Son intérêt mémoire disparaît si le consommateur matérialise tous les éléments. Une déclaration de type assure un contrat technique, pas la positivité d’une quantité ni les droits de l’utilisateur.
La portée d’une closure
Une fonction anonyme possède une portée locale. Pour utiliser certaines variables du contexte de création, elle doit les capturer explicitement avec use. Cette déclaration rend visible une dépendance qui serait sinon absente du corps. Une fonction fléchée capture automatiquement les variables utiles par valeur. Lors d’une revue, identifiez le moment de création et celui d’exécution : un callback peut être exécuté bien après la fin du bloc qui l’a construit.
Erreur fréquente à comprendre Toute variable extérieure est automatiquement locale à la closure.
La portée et la capture suivent des règles explicites.
Capturer une valeur au bon moment
Une capture par valeur retient la valeur de la variable lors de la création de la closure. Si un taux scalaire vaut 4 à cet instant, une réaffectation externe à 9 ne change pas la valeur capturée. C’est utile pour figer un paramètre de calcul. Cette règle ne signifie pas copie profonde : lorsqu’une valeur désigne un objet mutable, les méthodes de cet objet peuvent encore modifier son état partagé.
Erreur fréquente à comprendre use sans référence suit toujours toute réaffectation ultérieure externe.
La capture ne crée pas cette liaison générale.
Référence et état partagé
Une capture par référence relie la closure à la variable externe. Une réaffectation devient observable à travers cette liaison. Cela peut être voulu pour un compteur local, mais rend le résultat dépendant de l’ordre des appels. Documentez le propriétaire de cet état et sa durée de vie. Une référence n’ajoute ni verrou ni isolation ; dans un environnement où l’objet est réutilisé, elle peut conserver un contexte inattendu.
Erreur fréquente à comprendre Ajouter une référence rend tout état immuable.
La référence permet des effets partagés.
Les noms deviennent un contrat d’appel
Avec des arguments nommés, l’appelant désigne les paramètres par leur nom. Renommer un paramètre public peut donc casser un usage pourtant inchangé en nombre et en type d’arguments. Avant une modification d’API, recherchez ces appels et appliquez une politique de compatibilité. Le nom doit décrire la responsabilité du paramètre ; il ne faut pas le considérer comme un simple détail interne interchangeable à chaque refactorisation.
Erreur fréquente à comprendre Renommer un paramètre ne peut affecter aucun appel nommé.
Le nom peut faire partie du contrat consommé.
Variadiques, itérations et générateurs
Un paramètre variadique collecte un nombre variable d’arguments dans une structure accessible au corps de la fonction. Ce mécanisme ne doit pas être confondu avec un générateur. Une fonction utilisant yield produit des valeurs au fil de l’itération, ce qui permet un traitement progressif. Le bénéfice mémoire dépend du consommateur : convertir immédiatement tout le générateur en tableau recrée une accumulation complète. Testez aussi une itération interrompue avant la fin.
Erreur fréquente à comprendre Le variadique interdit tout nombre variable d’arguments.
C’est précisément son usage.
Le retour exprime une obligation
Un type de retour annonce ce que la fonction doit fournir, mais ne définit pas toutes les propriétés de ce résultat. Retourner un int ne prouve pas une quantité positive ni un stock suffisant. Placez les invariants au niveau où leur sens est connu. Lors d’un test, séparez incompatibilité de type, valeur hors borne et règle métier : ces échecs peuvent demander des messages et traitements différents.
Erreur fréquente à comprendre Un commentaire remplace toujours le contrat de retour natif.
Les déclarations natives sont vérifiées selon le langage.
Comprendre la durée d’un état local statique
Une variable statique locale conserve son état entre des appels dans le cycle où elle existe. Elle ne se comporte donc pas comme une variable locale ordinaire recréée à chaque entrée. Un cache ou compteur de ce type peut rendre des tests dépendants de leur ordre. Définissez sa portée réelle dans votre modèle d’exécution ; ne supposez pas qu’elle représente un stockage durable et coordonné entre plusieurs processus.
Erreur fréquente à comprendre Une variable statique locale est forcément réinitialisée à chaque appel.
Son cycle diffère d’une variable locale ordinaire.
Isoler le calcul des effets
Une fonction pure décrit idéalement un résultat à partir de ses entrées, sans lecture ou écriture externe cachée. Un commentaire ne rend pas cette propriété exécutoire. Injectez les dépendances nécessaires et séparez calcul, accès aux données et notification. Vous pourrez alors vérifier le calcul avec des valeurs fixes et tester les effets à une frontière dédiée, sans déclencher un email réel pour chaque combinaison de données.
Erreur fréquente à comprendre Déclarer une fonction pure dans un commentaire bloque toute écriture PHP.
La propriété demande une implémentation conforme.
Les valeurs par défaut ne dispensent pas de contrat
Un argument par défaut exprime une décision lorsque l’appelant omet une valeur. Cette expression doit suivre les possibilités autorisées par la version du langage. Choisissez une valeur dont le sens est clair : zéro peut signifier une quantité interdite plutôt qu’une absence. Pour un paramètre obligatoire au sens métier, un défaut permissif peut retarder l’erreur et produire un traitement avec des hypothèses jamais confirmées.
Erreur fréquente à comprendre Toute expression arbitraire est toujours autorisée comme défaut.
La syntaxe et le contexte imposent des contraintes.
Objet référencé ou copie indépendante
Affecter une variable contenant un objet permet généralement de désigner le même objet. Modifier son état par une référence peut donc être visible par l’autre. Une capture de cette valeur ne garantit pas une duplication récursive du graphe. Pour obtenir une indépendance, définissez une stratégie explicite de copie ou d’immuabilité. Vérifiez le cas des objets imbriqués ; une copie superficielle peut encore conserver des références partagées.
Erreur fréquente à comprendre Affecter un objet duplique récursivement tout son graphe.
Les références peuvent désigner le même objet.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
$n = 4;
$read = function () use ($n): int { return $n; };
$n = 9;
echo $read();
Le programme crée read lorsque n vaut 4. La clause use conserve cette valeur scalaire dans la closure. La ligne suivante remplace la valeur de la variable externe, mais ne réécrit pas la capture. L’appel affiche donc 4. Il n’y a ni variable absente ni conversion mystérieuse : les deux contextes possèdent simplement des états différents.
Refaites l’expérience avec une capture par référence et observez alors la réaffectation. Remplacez ensuite le nombre par un objet mutable pour distinguer valeur de référence et duplication de l’objet. Écrivez enfin un test qui crée deux callbacks avec deux taux différents et les appelle dans un ordre inversé. Le résultat doit rester cohérent avec la stratégie de capture que vous avez choisie.
Exemple commenté
<?php
declare(strict_types=1);
$rate = 2;
$price = fn (int $amount): int => $amount * $rate;
$rate = 3;
echo $price(5);
La fonction fléchée conserve la valeur 2 capturée à sa création et affiche 10. Elle ne suit pas la réaffectation ultérieure de rate.
Exercice de mise en pratique
Implémentez un générateur de lignes CSV qui ignore l’en-tête et ferme le fichier même si le consommateur s’arrête.
Voir une proposition de correction
Ouvrez le fichier dans le générateur, utilisez try/finally autour de la boucle fgetcsv et fermez la ressource dans finally. Testez un fichier vide et un arrêt anticipé.
- Identifier les mutations partagées.
- Éviter les dépendances globales.
- Mesurer la mémoire du consommateur aussi.
Contrôle de connaissances
Une fonction fléchée utilise $rate puis $rate est réaffecté. Que voit-elle ?
Réponse expliquée : La valeur capturée à sa création — La capture automatique se fait par valeur.
Cours associé : Fonctions, portée, closures et générateurs ↗
Documentation de référence ↗ https://www.php.net/manual/en/functions.arguments.php
Module 3 / 8 · 90 min
Objets, interfaces, enums et visibilité
Placer les invariants dans des opérations et choisir un contrat explicite.
Comprendre le mécanisme
Une interface décrit les opérations attendues ; une classe implémente leur comportement. Les traits réutilisent du code sans créer à eux seuls une relation de sous-typage. Les enums modélisent un ensemble fermé de cas. readonly empêche certaines réaffectations de propriétés, sans rendre transitivement immuable l’objet référencé. PHP 8.4 permet une visibilité d’écriture plus restrictive que la lecture. Les attributs attachent des métadonnées ; leur présence ne lance pas automatiquement un validateur.
Raisonner et vérifier
Une propriété publique facilite la lecture mais peut exposer un invariant à des écritures arbitraires. Préférez une méthode métier qui vérifie les transitions autorisées. Un namespace évite les collisions de noms ; l’autoload résout la classe seulement lorsqu’elle est demandée. Déclarez les types des propriétés et des constantes de classe afin de rendre les contrats vérifiables.
L’interface définit une substitution utile
Une interface exprime les opérations qu’un consommateur peut demander sans connaître l’implémentation concrète. Pour un envoi d’email, le même contrat peut être réalisé par un transport réel et par un double de test qui enregistre les messages. Le test vérifie alors les données envoyées sans contacter un fournisseur. La conformité au type reste distincte d’une autorisation : implémenter un contrat ne donne aucun droit utilisateur sur une ressource.
Erreur fréquente à comprendre Une interface donne automatiquement une autorisation métier.
Elle décrit des capacités de code, pas les droits d’un acteur.
Redéfinir sans rompre la promesse
Une classe enfant doit rester utilisable là où le contrat parent est attendu. Les signatures suivent des règles de compatibilité, notamment sur les types d’entrée et de sortie. Une modification qui rend un cas auparavant accepté impossible peut casser cette substitution. Commencez la revue par les appels du consommateur et les garanties promises, puis vérifiez les variantes autorisées par PHP. Le simple partage d’un nom de méthode ne garantit pas cette compatibilité.
Erreur fréquente à comprendre Une méthode enfant peut rendre tout contrat incompatible.
La substitution impose des règles de variance.
Readonly et mutabilité intérieure
Une propriété readonly limite sa réaffectation selon les règles de son initialisation. Si elle référence un ArrayObject, elle ne transforme pas cet objet en collection immuable. Une méthode de l’objet peut encore ajouter un élément. Pour protéger un invariant, choisissez aussi le modèle de l’objet référencé : copie contrôlée, valeur immuable ou API empêchant les mutations interdites. Testez séparément réaffectation de propriété et modification de l’objet intérieur.
Erreur fréquente à comprendre readonly gèle récursivement tout graphe d’objets.
La restriction de réaffectation ne prouve pas cette immutabilité profonde.
Lecture et écriture ont des portées distinctes
La visibilité contrôle des accès de code, pas la confidentialité cryptographique. En PHP 8.4, une propriété publique avec private(set) peut être lue publiquement tout en réservant son écriture au contexte autorisé de la classe. Cette séparation permet une consultation simple sans ouvrir une modification arbitraire. Elle ne remplace pas les méthodes métier : un changement permis techniquement doit encore respecter les invariants et les droits de l’acteur concerné.
Erreur fréquente à comprendre private chiffre la valeur en mémoire.
La visibilité n’est pas une primitive cryptographique.
Composer des membres avec un trait
Un trait apporte des membres à une classe et facilite une réutilisation ciblée. Ce n’est pas une instance autonome qui possède sa propre identité métier. Examinez les dépendances implicites du trait, les noms de méthodes attendus et les conflits possibles lors de la composition. Une duplication évitée ne justifie pas une dépendance cachée à toute la classe ; un petit service explicite peut parfois exprimer plus clairement la responsabilité partagée.
Erreur fréquente à comprendre Un trait est une instance autonome obligatoire.
Le mécanisme compose du code dans une classe.
Typer les constantes de classe
Une constante de classe peut annoncer un type natif dans les versions PHP qui le supportent. Le type rend son contrat plus visible et permet de refuser une valeur incompatible. Il ne vérifie pas toutes les relations métier entre plusieurs constantes. Par exemple, un délai entier peut rester négatif alors que le domaine l’interdit. La déclaration doit donc accompagner une conception cohérente et des tests sur les bornes utiles.
Erreur fréquente à comprendre Typer une constante remplace tous les tests métier.
Le type apporte un contrat limité à sa valeur.
Utiliser un enum comme vocabulaire fermé
Un enum représente un ensemble explicite de cas. Il convient aux états connus d’un traitement et aide à repérer les branches manquantes. Une chaîne reçue du réseau n’est toutefois pas déjà un cas valide : la conversion doit gérer une valeur inconnue. Séparez reconnaissance du vocabulaire et transition autorisée. Un état existant peut encore être interdit après un autre état selon le cycle de vie de la réservation.
Erreur fréquente à comprendre Un enum valide automatiquement tout texte reçu du réseau.
La conversion d’entrée peut échouer et doit être traitée.
Construire un objet déjà cohérent
Le constructeur doit établir les données nécessaires avant les opérations publiques. Si une réservation exige un identifiant et une quantité positive, un objet partiellement rempli crée une période où les autres méthodes doivent deviner ce qui manque. Préférez une construction qui refuse le cas invalide ou un modèle explicitement séparé pour le brouillon. Vérifiez aussi les objets référencés : leur mutabilité peut ensuite remettre en cause l’invariant initial.
Erreur fréquente à comprendre Le nom constructeur prouve à lui seul un état valide.
Le corps doit contrôler les conditions du domaine.
Composition ou héritage selon le contrat
Deux classes partageant un code ressemblant ne forment pas automatiquement une relation de substitution. La composition permet de confier une tâche à un collaborateur sans exposer toutes ses capacités par héritage. Dessinez les appels réellement nécessaires avant de choisir la structure. Si une classe n’utilise qu’un calculateur de tarif, injecter ce calculateur peut être plus précis qu’hériter d’un service possédant aussi stockage et notifications.
Erreur fréquente à comprendre Tout partage de code exige une hiérarchie de classes.
D’autres formes d’assemblage existent.
Ce que final garantit réellement
Une méthode final interdit une redéfinition selon le contrat du langage. Cela stabilise un point d’extension et peut protéger une séquence d’opérations choisie par la conception. La restriction n’accorde aucun privilège métier et ne prouve pas l’absence de bug dans le corps. Testez les propriétés utiles de la séquence, par exemple une vérification avant écriture, plutôt que de considérer le mot-clé comme une validation de sécurité générale.
Erreur fréquente à comprendre final rend l’instance autorisée sur toute ressource.
Le contrat du langage ne décide pas des permissions.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
final class Box {
public function __construct(public readonly ArrayObject $items) {}
}
$box = new Box(new ArrayObject());
$box->items->append('A');
echo count($box->items);
Box reçoit un ArrayObject puis conserve sa référence dans une propriété readonly. L’appel append ne remplace pas la référence : il demande une mutation à l’objet déjà désigné. Le comptage affiche donc un élément. Pour distinguer les deux opérations, essayez dans un laboratoire une réaffectation de la propriété vers un nouvel ArrayObject, puis comparez le résultat avec la mutation autorisée de l’objet intérieur.
La leçon de conception est de préciser ce que vous voulez rendre stable. Une référence stable peut suffire pour un collaborateur, mais pas pour une collection de valeurs qui doit rester inchangée. Construisez une petite API de collection en lecture seule si tel est le besoin, puis vérifiez qu’aucune référence mutable n’est exposée indirectement par un autre accesseur.
PHP 8.4 : lecture publique et écriture private(set)
final class Attempt {
public private(set) int $count = 0;
public function increment(): void { ++$this->count; }
}
$a = new Attempt();
$a->increment();
echo $a->count;
$a->count = 7;
La visibilité asymétrique distingue la lecture d’une propriété et la capacité de l’affecter. Dans une déclaration public private(set), le code extérieur peut lire la valeur, mais seul le contexte privé autorisé peut l’écrire. Une méthode de la classe peut donc faire évoluer cet état tout en exposant sa lecture.
Suivez le programme dans l’ordre : la méthode interne incrémente, la lecture affiche un, puis l’affectation extérieure tente une opération interdite et lève Error. La valeur affichée avant l’erreur ne signifie pas que toutes les instructions ont réussi.
Ce mécanisme ne doit pas être confondu avec readonly, qui répond à un autre contrat d’écriture. Il peut servir à préserver des invariants en imposant une méthode de transition. Testez les deux accès avec PHP 8.4 ou une version compatible et gardez l’autorisation métier séparée : une méthode publique qui modifie un paiement doit encore être appelée dans un contexte autorisé.
Points de vigilance : PHP 8.4 permet une visibilité d’écriture distincte sur une propriété typée. La méthode de la classe peut écrire ; le code extérieur peut lire mais pas affecter cette propriété. private(set) limite le lieu d’écriture : il n’impose pas la règle d’initialisation unique de readonly. Le modificateur public concerne ici la lecture, pas l’écriture externe.
Documentation de référence ↗
Exemple commenté
<?php
declare(strict_types=1);
final class Stock {
public const int MINIMUM = 0;
public private(set) int $quantity = 0;
public function add(int $amount): void {
if ($amount <= 0) { throw new InvalidArgumentException(); }
$this->quantity += $amount;
}
}
quantity peut être lue de l’extérieur ; son écriture est réservée à la classe. La méthode add impose en plus la positivité, que la visibilité seule ne garantit pas.
Exercice de mise en pratique
Ajoutez une opération reserve qui refuse une quantité insuffisante sans modifier l’état.
Voir une proposition de correction
Validez la demande, comparez au stock puis soustrayez uniquement en cas de succès. Testez refus, égalité et succès. Pour un stock persistant partagé, ajoutez aussi une protection transactionnelle.
- Séparer visibilité et validation.
- Ne pas confondre readonly et immutabilité profonde.
- Tester chaque transition d’enum métier.
Contrôle de connaissances
Que garantit private(set) sur une propriété publique PHP 8.4 ?
Réponse expliquée : Une lecture publique et une écriture restreinte à la classe — Les visibilités de lecture et d’écriture sont distinctes.
Cours associé : Objets, interfaces, enums et visibilité ↗
Documentation de référence ↗ https://www.php.net/manual/en/language.oop5.php
Module 4 / 8 · 90 min
Erreurs, fichiers, dates et formats de données
Gérer les ressources et les erreurs sans perdre le contrat des données.
Comprendre le mécanisme
Les exceptions remontent la pile jusqu’à un catch compatible. Throwable couvre notamment Exception et Error. finally sert au nettoyage, y compris lorsqu’un return a été évalué ; un return dans finally peut remplacer le résultat et masquer un problème. Un avertissement de fonction historique n’est pas nécessairement une exception. Testez le contrat précis de l’API utilisée. Un fichier ou un flux ouvert a un propriétaire responsable de sa fermeture.
Raisonner et vérifier
Pour les échanges JSON, activez les erreurs explicites et validez ensuite la structure. Un décodage réussi ne garantit ni les champs ni les types attendus. Conservez les identifiants opaques comme chaînes et évitez les calculs financiers en flottants binaires. Une date locale sans fuseau n’identifie pas toujours un instant unique ; stockez le contexte nécessaire et comparez les instants appropriés.
La hiérarchie des erreurs capturables
Throwable constitue le contrat commun aux branches Error et Exception. Capturer seulement Exception ne couvre donc pas tout ce qui peut remonter comme Throwable. Choisissez la frontière de capture selon votre responsabilité : une erreur de programmation ne doit pas être silencieusement convertie en succès métier. Un gestionnaire global peut produire une réponse publique contrôlée tout en conservant un diagnostic privé pour l’investigation.
Erreur fréquente à comprendre catch Exception intercepte nécessairement tout Error.
La hiérarchie comporte des branches distinctes.
Traiter au niveau qui peut décider
Attraper une exception n’est pas encore la résoudre. Le niveau de traitement doit savoir s’il peut reprendre, annuler ou traduire l’échec. Un repository ne doit pas prétendre que la commande a réussi après une erreur de stockage. Conservez la cause utile au diagnostic, puis exposez un contrat d’erreur stable à la couche suivante. Les reprises doivent tenir compte des effets déjà appliqués avant l’exception.
Erreur fréquente à comprendre Ignorer toute exception assure une transaction correcte.
Le masquage peut cacher un état incohérent.
Le nettoyage ne doit pas changer le résultat
finally sert à exécuter un nettoyage dans le flux prévu, mais un return placé dans ce bloc peut remplacer un résultat ou masquer une exception. Gardez donc la libération des ressources séparée de la décision de succès. Dans un laboratoire, lancez une exception dans try puis ajoutez un return dans finally : observer la disparition de l’erreur montre pourquoi ce raccourci est dangereux pour une transaction.
Erreur fréquente à comprendre finally garantit le succès métier après une exception.
Le nettoyage ne prouve pas la réussite de l’opération.
Syntaxe JSON et modèle métier
Un document JSON valide peut contenir une chaîne là où le domaine demande un entier. Après le parsing, vérifiez clés, types, bornes et relations entre champs. Pour un identifiant plus grand que PHP_INT_MAX dont il faut garder tous les chiffres, définissez un contrat texte et utilisez les options de décodage adaptées, notamment JSON_BIGINT_AS_STRING. Convertir trop tôt en float peut perdre une information qui ne pourra plus être reconstruite exactement.
Erreur fréquente à comprendre json_decode réussi prouve toutes les clés métier.
Les types, clés et invariants demandent une validation.
Une erreur publique utile et limitée
Un client a besoin d’un code stable et d’une explication actionnable, pas des chemins internes, du DSN et des secrets de connexion. Séparez la réponse publique d’un événement privé corrélé. L’identifiant de corrélation peut être non sensible et borné. Testez les erreurs de validation et les pannes inattendues : le chemin exceptionnel ne doit pas devenir le seul endroit qui expose une trace complète ou des credentials.
Erreur fréquente à comprendre Une API doit afficher toute trace et tout DSN.
Ces informations appartiennent au diagnostic privé contrôlé.
Fermer selon le cycle de ressource
Un fichier, une connexion ou un flux possède une durée utile indépendante de la durée apparente de la fonction. Prévoyez la fermeture sur succès et sur échec, en respectant l’API concernée. Un nettoyage qui échoue peut lui-même demander un diagnostic sans masquer la cause initiale. Les tests doivent simuler une erreur pendant le traitement ; un seul chemin nominal ne démontre pas que la ressource est libérée au bon moment.
Erreur fréquente à comprendre Une exception rend inutile toute politique de fermeture.
Les chemins d’échec consomment aussi des ressources.
Reconnaître les conventions de retour
Les API ne signalent pas toutes un échec par une exception. Certaines retournent false, une valeur spéciale ou un code qu’il faut examiner. Lisez le contrat de la fonction puis utilisez une comparaison qui conserve les valeurs valides, notamment zéro. Évitez de transformer tout résultat faux au sens booléen en erreur : une recherche peut avoir réussi au premier indice. Le traitement doit distinguer cette réussite d’une absence réelle.
Erreur fréquente à comprendre Toute fonction signalant false lance toujours une exception.
Le mode d’erreur dépend de l’API et de sa configuration.
Valider avant les effets coûteux
Le parsing et la validation doivent précéder les effets qui ne peuvent pas être simplement annulés. Envoyer un email puis découvrir que l’adresse ou la permission était invalide laisse déjà une conséquence externe. Organisez le traitement en étapes : données structurées, invariants, droit, transition durable et effets coordonnés. Cette séparation permet de tester qu’un refus ne produit ni écriture ni appel aval interdit.
Erreur fréquente à comprendre Une entrée invalide peut être refusée après tout envoi externe.
L’ordre conditionne l’absence d’effet interdit.
Construire des classes d’entrées de test
Un exemple nominal ne couvre pas une frontière. Préparez une petite matrice contenant clé absente, null, valeur vide, mauvais type et valeurs aux bornes. Pour chaque cas, écrivez le résultat attendu et l’effet interdit. Cette démarche évite une accumulation d’exemples arbitraires : les tests représentent des distinctions du contrat, et leur nom explique pourquoi cette catégorie doit être acceptée ou refusée.
Erreur fréquente à comprendre Un exemple valide prouve tous les chemins d’erreur.
Les cas négatifs demandent leurs propres assertions.
La désérialisation ne donne pas la provenance
Lire une représentation ne démontre pas qui l’a produite. Une préférence contenue dans un cookie reste une entrée client même si elle a déjà été stockée auparavant. Dans une nouvelle API, préférez un format de données simple avec validation explicite plutôt que traiter librement une sérialisation d’objets non fiable. La reconstruction d’objets peut introduire des comportements et dépendances qui dépassent le besoin d’une préférence textuelle.
Erreur fréquente à comprendre Toute donnée désérialisée est authentifiée.
Lire une représentation ne prouve pas sa provenance.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
$data = json_decode('{"amount":"12"}', true, flags: JSON_THROW_ON_ERROR);
echo is_int($data['amount']) ? 'accepted' : 'rejected';
Le document de l’exercice contient amount entre guillemets. Le parseur JSON établit que le document est syntaxiquement lisible ; il conserve ensuite amount comme une chaîne. is_int retourne false et le programme affiche rejected. Aucun JsonException n’est requis pour expliquer ce résultat : le défaut se situe dans le contrat métier du contenu, après le parsing réussi.
Rejouez le programme avec un entier positif, zéro, null et une clé absente. Ajoutez une garde de présence avant la lecture pour que le dernier cas soit traité proprement. Écrivez ensuite une matrice qui distingue rejet de type et rejet de borne. Si votre produit accepte volontairement une chaîne numérique, définissez la grammaire et les bornes de cette conversion au lieu de compter sur un cast implicite.
Préférences client : JSON, schéma et absence de privilège
$preferences = unserialize($_COOKIE["prefs"]);
Un cookie est contrôlable par son porteur. Unserialize sur une entrée non fiable peut déclencher des comportements liés aux objets et à leurs méthodes magiques selon le code disponible ; ce n’est pas un simple décodeur de données interchangeables.
Pour une nouvelle implémentation de préférences, choisissez un format de données comme JSON, bornez taille et profondeur et validez un schéma étroit. Par exemple, theme peut appartenir à une liste claire et langue à une liste supportée. Une structure syntaxiquement correcte ne doit pas permettre d’introduire rôle, tenant ou chemin de fichier.
Les droits proviennent du contexte serveur authentifié et autorisé, jamais du nom des clés du cookie. Testez mauvais type, clé inconnue, dépassement et JSON invalide. Le refus ou le défaut prévu doit être explicite et ne doit pas devenir un privilège de repli. Cette conception traite à la fois les capacités du parseur et la confiance accordée aux données.
Points de vigilance : Le manuel déconseille les données non fiables même avec cette option ; elle n’assure pas la sûreté globale. Un format de données sans instanciation d’objets PHP réduit le risque ; validation et frontière de confiance restent nécessaires. Base64 est réversible et n’authentifie ni ne neutralise la structure sérialisée. Une exception n’annule pas nécessairement les effets déclenchés pendant une désérialisation.
Documentation de référence ↗
Identifiant PSP hors plage : conserver les chiffres comme chaîne
{"transactionId":12345678901234567890}
Un identifiant peut ressembler à un nombre sans avoir de sens arithmétique. Si l’entier JSON dépasse PHP_INT_MAX, un décodage numérique ordinaire peut perdre la précision en passant par une représentation flottante. Cette perte peut faire rechercher ou rapprocher la mauvaise opération.
JSON_BIGINT_AS_STRING préserve la représentation de cet entier hors plage sous forme de chaîne ; JSON_THROW_ON_ERROR permet de traiter une syntaxe invalide par une exception explicite. Après parsing, vérifiez le schéma, le type attendu et les règles de format, puis conservez l’identifiant comme chaîne dans le stockage et les échanges PHP/Java.
Ne convertissez pas ensuite en float pour normaliser, trier ou exporter. Un test doit vérifier l’égalité exacte de tous les chiffres après décodage, persistance et sérialisation. Ajoutez une valeur plus petite et une entrée malformée. Le flag de parsing protège cette représentation, mais ne donne aucune autorisation d’accès à l’opération identifiée.
Points de vigilance : Le flag préserve cet entier hors plage sous forme de chaîne ; le schéma doit ensuite être contrôlé. Le float peut déjà avoir perdu des chiffres avant la conversion en chaîne. L’identifiant dépasse la plage de int ; le cast ne garantit pas sa fidélité. JSON_THROW_ON_ERROR signale des erreurs de décodage, pas cette perte de précision autorisée.
Documentation de référence ↗
Exemple commenté
<?php
declare(strict_types=1);
try {
$data = json_decode('{bad json}', true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
echo 'Format JSON invalide';
}
Le JSON invalide provoque JsonException. Le gestionnaire peut traduire cette erreur en réponse client sans exposer une trace interne.
Exercice de mise en pratique
Parsez un document JSON de réservation et refusez un objet sans quantity, un grand identifiant numérique et un document mal formé.
Voir une proposition de correction
Utilisez JSON_THROW_ON_ERROR et JSON_BIGINT_AS_STRING, puis vérifiez le schéma. Séparez erreur de décodage et erreur métier. Un identifiant opaque ne doit pas subir de conversion numérique ultérieure.
- Ne pas avaler toutes les erreurs silencieusement.
- Fermer les ressources avec finally.
- Tester fuseaux et entrées mal formées.
Contrôle de connaissances
Quelle famille permet de capturer Exception et Error ?
Réponse expliquée : Throwable — Les deux familles implémentent Throwable.
Cours associé : Erreurs, fichiers, dates et formats de données ↗
Documentation de référence ↗ https://www.php.net/exceptions
Module 5 / 8 · 90 min
HTTP, formulaires, sessions et uploads
Séparer données client, identité de session et autorisation.
Comprendre le mécanisme
HTTP transporte des requêtes indépendantes ; une session ajoute un état serveur associé à un identifiant. Un cookie reçu reste une donnée du client, même si son nom semble interne. Secure limite l’envoi du cookie à HTTPS et HttpOnly son accès direct par JavaScript ; aucun de ces attributs ne décide des autorisations métier. Après une authentification ou une élévation de privilèges, prévoyez une rotation de l’identifiant avec gestion des requêtes concurrentes.
Raisonner et vérifier
Les superglobales contiennent des entrées non fiables. Validez taille, forme et droits avant toute opération. Pour un upload, contrôlez l’erreur de transfert, la taille réelle, le type détecté et le contenu attendu ; générez un nom serveur et empêchez l’exécution dans le répertoire de stockage. Réduisez la durée du verrou de session en fermant la session dès que les modifications utiles sont enregistrées.
Une session est un état accepté par le serveur
Le cookie de session transporte souvent une référence opaque, pas une preuve autonome de tous les droits. Le serveur résout cette référence et applique ses règles de validité, d’expiration et d’activation du compte. Définissez les limites de durée et ce qui arrive après une désactivation. Une date côté navigateur ne suffit pas : un détenteur d’une ancienne copie peut encore présenter la même référence à un autre client.
Erreur fréquente à comprendre Le cookie client décide seul de tout droit serveur.
Le serveur conserve la politique d’identité et d’accès.
Changer de contexte d’identité
Le passage d’une session anonyme à une session authentifiée change les capacités associées à son identifiant. Une politique de rotation doit empêcher qu’un identifiant choisi ou conservé par un tiers devienne une capacité privilégiée. Vérifiez l’ancien et le nouvel identifiant après la transition. Le succès du formulaire de connexion ne prouve pas, à lui seul, que l’ancienne référence a perdu tous les usages interdits.
Erreur fréquente à comprendre Un identifiant anonyme doit toujours rester utilisable après login.
La fixation doit être traitée selon la transition.
Une lecture ne doit pas demander une annulation
GET sert à demander une représentation selon une sémantique sûre. Les navigateurs, outils de préchargement et robots peuvent effectuer cette lecture sans clic volontaire d’annulation. Placez les mutations derrière une méthode et un contrat adaptés, puis vérifiez identité, permission et état métier. Changer seulement le texte d’un lien ne corrige pas une route GET dont le serveur continue à supprimer une ressource.
Erreur fréquente à comprendre GET est idéal pour supprimer une commande.
Les lectures peuvent être déclenchées automatiquement.
La défense CSRF précède l’effet
Un navigateur peut envoyer automatiquement son cookie lors d’un parcours déclenché ailleurs. Le serveur doit donc distinguer une demande conforme à son contexte de formulaire d’une demande forgée. Vérifiez le jeton CSRF et les autres contrôles requis avant l’écriture. Les tests doivent fournir valeur correcte, valeur absente et valeur incorrecte, puis vérifier le statut et l’absence de mutation dans les deux derniers cas.
Erreur fréquente à comprendre Un cookie présent suffit à prouver un clic volontaire.
Le navigateur peut envoyer des credentials automatiquement.
Portée précise de HttpOnly
HttpOnly limite la lecture directe du cookie par les API JavaScript habituelles. Cette propriété réduit une voie de vol, mais ne neutralise pas toutes les conséquences d’un script hostile exécuté dans l’origine. Le script peut encore demander des actions au serveur. Conservez donc prévention XSS, contrôles d’autorisation et politique de session ; ne traduisez pas un attribut de cookie en garantie générale de sécurité du navigateur.
Erreur fréquente à comprendre HttpOnly supprime toute conséquence d’une XSS.
Le script peut encore agir dans l’origine compromise.
Transport sécurisé et capacité sensible
Secure demande un envoi du cookie dans les conditions de transport sécurisé prévues par le navigateur. Le contenu opaque reste une capacité sensible s’il peut être rejoué. L’attribut ne dit rien sur le propriétaire d’une réservation ni sur la validité actuelle du compte. Vérifiez la réponse réellement déployée et le contexte proxy : une mauvaise perception du schéma peut influencer les cookies et URL générés.
Erreur fréquente à comprendre Secure autorise tout utilisateur comme administrateur.
L’attribut ne décide pas des droits métier.
Construire la réponse et servir les fichiers
Les en-têtes appartiennent au message HTTP et doivent être construits avant l’émission incompatible du corps selon le contexte PHP. Utilisez les API du framework pour statuts, cookies et téléchargements. Pour un upload destiné au public, choisissez un nom généré côté serveur et un espace sans exécution de code. Le nom fourni par le client ne doit pas devenir librement un chemin de stockage ou une règle de présentation.
Erreur fréquente à comprendre Une sortie déjà envoyée ne peut jamais gêner la gestion des headers.
Le cycle d’émission des champs doit être respecté.
Statuts et chemins d’erreur
Un statut donne une information protocolaire utile aux clients et à la supervision. Ne transformez pas toute panne en réponse 200. Définissez un schéma d’erreur public et une corrélation privée sans exposer les détails internes. Un refus de droit, une entrée invalide et une dépendance indisponible n’impliquent pas toujours la même reprise. Testez le corps autant que le statut, notamment sur les erreurs qui contournent le chemin nominal.
Erreur fréquente à comprendre Toute erreur doit devenir 200 pour les clients.
Les clients et outils utilisent aussi le statut.
Session, concurrence et cache
Une session peut introduire une contention si son mécanisme de stockage conserve un verrou pendant un traitement long. Une fois la lecture et les écritures de session utiles terminées, fermer tôt la session peut réduire l’attente des requêtes parallèles ; vérifiez le contrat du handler utilisé. Séparez cette question de celle du cache : une réponse personnalisée ne doit pas être distribuée à un autre compte par une clé trop générale.
Erreur fréquente à comprendre Toutes les sessions peuvent partager la même réponse privée.
La frontière inter-utilisateurs doit être préservée.
Le logout n’est pas une suppression universelle
Effacer un cookie sur un navigateur retire cette copie locale. Cela ne prouve pas le retrait des autres copies ni de tous les états de validation. Définissez ce que le logout doit invalider côté serveur et mesurez sa propagation si plusieurs instances participent. Un test avec deux clients permet de vérifier la distinction : fermer le premier ne suffit pas à démontrer que le second ne peut plus utiliser l’ancienne capacité.
Erreur fréquente à comprendre Fermer un navigateur invalide tous les tokens ailleurs.
Le retrait serveur doit avoir un mécanisme explicite.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
$expected = 'lab-A';
$provided = 'lab-B';
$writes = 0;
if (hash_equals($expected, $provided)) { ++$writes; }
echo $writes;
Le petit programme de validation CSRF compare deux chaînes distinctes. La branche contenant l’incrément n’est pas exécutée et writes reste égal à zéro. Le point important est l’ordre : aucune écriture ne précède la comparaison. Remplacer la réponse finale par un statut de refus après un incrément ne donnerait pas la même propriété, même si le navigateur affichait une erreur.
Déplacez volontairement l’effet avant la garde dans une copie de laboratoire et écrivez un test qui observe le compteur. Ce test doit échouer sur la version incorrecte. Ajoutez ensuite un contrôle d’autorisation indépendant : un token valide ne doit pas permettre à un acteur d’annuler une réservation d’un autre compte. Vous aurez alors deux preuves séparées pour deux frontières différentes.
Exemple commenté
<?php
session_start([
'use_strict_mode' => true,
'cookie_secure' => true,
'cookie_httponly' => true,
'cookie_samesite' => 'Lax',
]);
$memberId = $_SESSION['member_id'] ?? null;
session_write_close();
Ce fragment configure des protections de transport et libère rapidement le verrou. L’existence de memberId ne remplace pas le contrôle d’accès à la réservation demandée.
Exercice de mise en pratique
Décrivez les contrôles d’un endpoint qui reçoit une image de réservation. Incluez un test de fichier PHP renommé en JPEG.
Voir une proposition de correction
Vérifiez le fichier reçu et son type réel, limitez les dimensions et octets, attribuez un nom non fourni par le client et stockez dans un espace sans exécution. Refusez le fichier trompeur avant publication.
- Ne jamais faire confiance au MIME déclaré seul.
- Gérer fixation et renouvellement de session.
- Autoriser chaque ressource côté serveur.
Contrôle de connaissances
Quel attribut limite la lecture directe d’un cookie par JavaScript ?
Réponse expliquée : HttpOnly — HttpOnly empêche l’accès via les API JavaScript de cookies.
Cours associé : HTTP, formulaires, sessions et uploads ↗
Documentation de référence ↗ https://www.php.net/manual/en/features.session.security.management.php
Module 6 / 8 · 90 min
PDO, SQL et cohérence transactionnelle
Séparer structure SQL et valeurs puis protéger les invariants concurrents.
Comprendre le mécanisme
Une requête préparée maintient les valeurs en dehors de la structure SQL. Un paramètre ne représente pas un nom de table ou une direction de tri : ces fragments exigent une liste fermée contrôlée par le serveur. La transaction regroupe les changements qui doivent réussir ou échouer ensemble. Elle ne transforme pas toute séquence lecture-écriture en invariant concurrent sûr : le niveau d’isolation, les contraintes et les verrous doivent correspondre à la règle.
Raisonner et vérifier
Définissez le propriétaire de la transaction et son traitement des exceptions. Une contrainte unique est plus fiable qu’un simple SELECT préalable pour empêcher les doublons concurrents. Préparez des tests avec deux connexions indépendantes et vérifiez le nombre de lignes modifiées. Les droits SQL du compte applicatif doivent être limités au périmètre nécessaire ; les secrets de connexion restent hors du dépôt.
Séparer les données de la grammaire SQL
Un paramètre de requête représente une valeur de donnée. Il ne remplace pas librement un nom de colonne, une direction de tri ou un fragment SQL. Pour ORDER BY, traduisez un choix métier en fragment issu d’une liste fermée. Pour une liste de trois identifiants, construisez trois placeholders distincts et liez chaque valeur. Réutiliser une donnée enregistrée précédemment ne la rend pas sûre comme grammaire : l’injection peut aussi survenir lors d’une seconde utilisation.
Erreur fréquente à comprendre Un paramètre PDO peut remplacer librement tout nom de table.
Les identifiants SQL demandent une politique distincte.
Choisir la frontière atomique
Une transaction doit couvrir les écritures qui forment une même transition cohérente. Réserver du stock et créer la réservation correspondante demandent généralement une décision commune dans le stockage concerné. L’atomicité SQL ne s’étend pas automatiquement à un email ou à un PSP distant. Identifiez les frontières de systèmes avant de promettre un tout ou rien ; les effets externes demandent une coordination ou une procédure de reprise adaptée.
Erreur fréquente à comprendre Une transaction locale inclut automatiquement un email externe.
Les systèmes distants n’entrent pas dans le même commit par défaut.
Échouer sans valider un état partiel
Une exception ne réalise pas toute la politique de transaction à votre place. Le code doit connaître l’état de la transaction et appliquer l’annulation prévue, sans annoncer un succès à l’appelant. Testez une panne entre les écritures liées : ni le stock ni la réservation ne doivent rester dans un état contradictoire. Conservez un diagnostic privé et évitez un nettoyage qui masque la première cause d’échec.
Erreur fréquente à comprendre Attraper une exception valide automatiquement la transaction.
L’état doit suivre un traitement explicite.
Unicité et concurrence réelle
Deux clients peuvent lire simultanément qu’une clé est absente, puis tenter chacun de l’insérer. La séquence de vérification applicative seule ne garantit donc pas l’unicité. Placez une contrainte UNIQUE dans la base et définissez comment l’application traduit son refus en résultat métier. Le test utile emploie des demandes concurrentes ou une interposition contrôlée ; un seul parcours séquentiel ne reproduit pas cette course.
Erreur fréquente à comprendre Lire puis insérer sans contrainte garantit toute unicité.
Deux transactions peuvent passer le même précontrôle.
Connaître le mode d’erreur utilisé
Les bibliothèques peuvent signaler leurs échecs par exceptions ou valeurs de retour selon leur configuration et leur version. Rendez le mode PDO attendu explicite et vérifiez les chemins de traitement. Un code qui suppose une exception peut ignorer un false, et un code qui capture toute erreur peut masquer un défaut de programmation. La politique doit distinguer refus de donnée, conflit attendu et panne de dépendance.
Erreur fréquente à comprendre PDO lance toujours une exception dans toute configuration historique.
Le contrat effectif doit être connu.
Driver natif et environnement cible
PDO est une interface dont les drivers dépendent des extensions de l’interpréteur réellement exécuté. Un paquet Composer ne fournit pas automatiquement pdo_mysql ou un autre driver natif. Vérifiez le binaire CLI utilisé par le déploiement et la configuration du PHP web séparément. Une commande réussie dans un shell ne démontre pas que PHP-FPM utilise les mêmes fichiers de configuration, modules chargés ou variables d’environnement.
Erreur fréquente à comprendre Installer une bibliothèque Composer fournit automatiquement tout driver PDO.
Le driver est une extension de l’interpréteur.
Stabiliser une page de résultats
LIMIT ne définit pas un ordre de lignes. Pour une pagination reproductible, choisissez un ORDER BY pertinent et un critère de départage lorsque plusieurs lignes partagent la même valeur principale. Le plan et les données peuvent évoluer entre deux appels. Définissez aussi l’effet des insertions concurrentes sur votre contrat de pagination ; une approche par curseur peut exprimer un parcours différent d’un simple décalage numérique.
Erreur fréquente à comprendre LIMIT sans ordre garantit les mêmes lignes à chaque fois.
La sélection peut varier selon le plan et les données.
Réduire les capacités du compte SQL
Le compte applicatif ne devrait posséder que les opérations utiles à son rôle runtime. Les migrations et l’administration peuvent demander un compte distinct. Cette séparation limite les conséquences d’une erreur ou d’une compromission sans prétendre empêcher toute attaque. Vérifiez avec le rôle réellement utilisé : un test lancé en superutilisateur peut réussir alors qu’il ne représente pas les permissions et contraintes de la production.
Erreur fréquente à comprendre Une API doit toujours se connecter en superutilisateur.
Le périmètre de privilège doit rester limité.
Reprendre après une réponse perdue
Un timeout laisse parfois un résultat incertain : le moteur peut avoir validé le commit avant que la réponse ne soit perdue. Ne rejouez pas aveuglément une création sensible sous une nouvelle identité de demande. Concevez une référence idempotente et une consultation de résultat durable lorsque le contrat le demande. Le test déterminant place la panne après la validation de l’effet, pas seulement avant le début de la transaction.
Erreur fréquente à comprendre Un silence réseau prouve nécessairement le rollback distant.
La réponse peut se perdre après validation.
Diagnostiquer sans exposer le DSN
Les erreurs de connexion et de requête peuvent révéler noms de base, chemins, valeurs métier et credentials. Une réponse publique doit rester limitée à un code stable et à une corrélation non sensible. Conservez le diagnostic détaillé dans un espace restreint selon la politique de logs. Examinez également les outils de déploiement : un affichage de variables complet peut divulguer le secret avant même le démarrage de l’application.
Erreur fréquente à comprendre Une exception PDO doit afficher le mot de passe du DSN.
Le diagnostic doit être protégé et minimisé.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
$sort = 'created_at'; // valeur reçue dans cet exercice
$sql = 'SELECT id FROM orders WHERE tenant_id = :tenant ORDER BY '.$sort;
echo $sql;
L’exercice construit un texte SQL dont tenant_id est destiné à recevoir une valeur liée, mais dont la colonne de tri est concaténée. La présence d’un placeholder ailleurs dans la requête ne protège pas ce fragment de grammaire. Le correctif consiste à traduire, par exemple, le choix métier recent en un fragment fixe connu, puis à refuser un choix hors liste avant l’exécution.
Dans le laboratoire, préparez deux choix autorisés et une entrée contenant une expression SQL arbitraire. Vérifiez que cette dernière n’est jamais transmise au moteur. Ajoutez ensuite un filtre de liste en construisant un placeholder par identifiant. Testez la liste vide séparément : son interprétation appartient au contrat de l’API et ne doit pas être improvisée par une concaténation invalide.
Atelier PDO : trier avec une liste fermée
$sql = "SELECT id, total FROM invoice ORDER BY " . $_GET["sort"];
Un placeholder représente une valeur, pas un identifiant SQL ni une direction de tri. ORDER BY :column ne permet donc pas de substituer de manière générale le nom d’une colonne. Le besoin est ici de laisser choisir une option fonctionnelle, tout en gardant la structure SQL sous contrôle du serveur.
Construisez un tableau qui associe recent à un fragment constant comme created_at DESC, id DESC, et ancien à l’ordre inverse. Recherchez la clé reçue dans cette table et refusez toute clé inconnue. Les filtres de valeurs, tels que tenant et statut, restent liés séparément par placeholders.
Testez une clé nominale, une clé absente et une entrée contenant du SQL. Le refus doit précéder l’exécution, et les résultats du chemin nominal doivent avoir un ordre déterministe en cas de dates identiques. Cette séparation évite de demander à l’échappement de données de décider de la grammaire de la requête.
Points de vigilance : Un paramètre représente une valeur, pas un nom de colonne ni un mot-clé de tri. La liste fermée maintient la structure SQL sous contrôle du serveur ; les valeurs restent paramétrées. Un encodage HTML ne protège pas la grammaire SQL. PDO::quote produit un littéral de chaîne, pas un identifiant de colonne autorisé.
Documentation de référence ↗
Atelier PDO : une liste de valeurs dans IN
SELECT id FROM invoice WHERE id IN (...)
Une liste SQL contient plusieurs expressions. Lier la chaîne 1,2,3 dans un seul placeholder fournit une seule valeur, pas trois identifiants. Le serveur doit construire uniquement le nombre de marqueurs nécessaire, tandis que le contenu de chaque élément reste une donnée liée.
Validez d’abord que l’entrée est une liste non vide, de taille bornée, dont les identifiants respectent le contrat. Produisez ensuite trois marqueurs pour trois éléments, par exemple IN (?, ?, ?), et passez les trois valeurs séparément. Aucun identifiant reçu ne doit être concaténé dans le SQL.
Définissez aussi le comportement de la liste vide : refus ou résultat vide selon l’API, sans générer un IN () improvisé. Testez un élément, plusieurs éléments, doublons et taille excessive. La portée tenant reste une condition serveur distincte ; une liste d’identifiants bien typés peut encore contenir des objets interdits à l’acteur.
Points de vigilance : Une chaîne contenant des virgules reste une seule valeur, pas une liste SQL. Contrôler la longueur de la liste ne valide ni les éléments ni la structure concaténée. PDO ne développe pas automatiquement un tableau PHP en liste de paramètres. Chaque valeur garde sa place de donnée ; le nombre de placeholders vient du nombre d’éléments, pas de leur contenu.
Documentation de référence ↗
Injection de second ordre : une donnée stockée reste une donnée
$name = $savedReport["name"];
$sql = "SELECT * FROM audit WHERE report_name = '" . $name . "'";
Une insertion paramétrée protège l’interprétation SQL de cette insertion. Elle ne transforme pas son contenu en fragment SQL sûr pour toutes les lectures futures. Une valeur peut être enregistrée sans effet puis devenir dangereuse lorsqu’un traitement administratif la concatène dans une nouvelle requête.
Suivez le trajet du nom de rapport : entrée utilisateur, stockage, relecture et nouvelle destination SQL. À la dernière étape, utilisez encore un placeholder si le nom est une valeur recherchée. Si le besoin porte sur un identifiant ou une structure de requête, résolvez une référence dans une liste de fragments constants maîtrisés.
Un test utile enregistre une chaîne factice contenant des apostrophes puis déclenche le traitement différé. Vérifiez que la chaîne est recherchée littéralement et qu’aucune structure de requête n’est ajoutée. Les traitements internes, exports et tâches cron doivent respecter les mêmes frontières de données que les contrôleurs publics.
Points de vigilance : La sécurité de l’insertion ne se transmet pas à un autre contexte d’interprétation SQL. Le stockage n’enlève pas la signification SQL de caractères réutilisés par concaténation. Un filtre partiel fragilise les données légitimes et ne remplace pas la séparation code/données. Un accès en lecture peut déjà permettre une extraction ou un contournement de filtre.
Documentation de référence ↗
Exemple commenté
<?php
// $pdo est une connexion de laboratoire configurée.
$stmt = $pdo->prepare('UPDATE stock SET quantity = quantity - :n WHERE id = :id AND quantity >= :required');
$stmt->execute(['n' => 2, 'id' => 7, 'required' => 2]);
if ($stmt->rowCount() !== 1) { throw new RuntimeException('Stock indisponible'); }
La condition et la modification sont réalisées dans la même instruction. Ajoutez une transaction lorsque la réservation et son historique doivent être enregistrés ensemble.
Exercice de mise en pratique
Ajoutez une clé de requête unique pour empêcher un double traitement de la même réservation.
Voir une proposition de correction
Créez une contrainte unique sur la clé métier. Dans la transaction, distinguez la répétition de la même demande d’une réutilisation avec un contenu différent. Testez les insertions concurrentes.
- Lier les valeurs, autoriser les identifiants.
- Écrire les invariants dans des contraintes ou mises à jour atomiques.
- Tester l’échec et le rollback.
Contrôle de connaissances
Peut-on paramétrer directement un nom de colonne avec PDO ?
Réponse expliquée : Non, il faut une liste fermée de fragments autorisés — Les paramètres représentent des valeurs SQL.
Cours associé : PDO, SQL et cohérence transactionnelle ↗
Documentation de référence ↗ https://www.php.net/manual/en/pdo.prepared-statements.php
Module 7 / 8 · 90 min
Sécurité aux frontières de confiance
Choisir la protection adaptée au contexte d’interprétation.
Comprendre le mécanisme
Une donnée traverse plusieurs contextes : SQL, HTML, chemins, logs ou commandes. Un encodage adapté à l’un ne protège pas les autres. Pour HTML, l’échappement de sortie conserve les données comme texte ; pour SQL, les valeurs sont liées ; pour une commande, réduisez d’abord le besoin d’exécution et évitez la construction libre d’une ligne shell. L’autorisation répond à la question « cet acteur peut-il agir sur cette ressource ? », distincte de l’authentification.
Raisonner et vérifier
Hachez les mots de passe avec une API dédiée et vérifiez-les avec password_verify. Un token CSRF protège une action contre des déclenchements intersites indésirables, pas contre toutes les attaques. Évitez de désérialiser des objets issus d’une source non fiable. Les erreurs publiques doivent être utiles sans divulguer secrets, traces SQL ou tokens ; les journaux gardent des événements minimisés et un identifiant de corrélation.
Vérifier un mot de passe selon son format
password_hash crée un format adapté à la vérification d’un secret humain, avec ses paramètres et son sel. password_verify vérifie ensuite une valeur présentée contre ce format. Un hash rapide comme MD5 ne fournit pas un coût approprié de résistance aux essais hors ligne. Après succès, une politique peut demander un rehash ; un échec ne doit jamais remplacer la preuve stockée par la mauvaise valeur reçue.
Erreur fréquente à comprendre MD5 direct fournit un coût adaptatif approprié.
Une fonction dédiée doit ralentir et paramétrer les essais.
La sortie détermine le contrôle pertinent
Une chaîne peut être destinée à du texte HTML, une URL, du JavaScript ou une valeur SQL. Ces contextes ne partagent pas une protection universelle. htmlspecialchars ne transforme pas une concaténation SQL en requête paramétrée. Pour un libellé navigateur, une insertion textuelle peut éviter l’interprétation HTML. Commencez chaque revue par le point d’utilisation de la donnée, puis choisissez le contrôle qui correspond à ce contexte précis.
Erreur fréquente à comprendre Un échappement HTML sécurise tout SQL.
Les contextes exigent des contrôles différents.
L’objet fait partie de l’autorisation
Un utilisateur connecté n’obtient pas automatiquement l’accès à une réservation dont il connaît l’identifiant. Les UUID et paramètres SQL protègent d’autres propriétés que l’isolation métier. Résolvez la ressource dans le bon tenant et vérifiez l’opération demandée. Le test doit essayer deux acteurs avec le même identifiant et observer le refus sans effet du second ; une interface qui masque le lien ne bloque pas un client direct.
Erreur fréquente à comprendre Une identité connectée peut lire toute ligne par son ID.
L’authentification ne donne pas tous les droits.
Les références reçues ne donnent pas accès au système
Un chemin fourni par le client ne doit pas sélectionner librement un fichier serveur. Préférez un identifiant métier résolu dans un stockage maîtrisé, avec autorisation avant lecture. La même prudence vaut pour une préférence sérialisée : un texte reçu ne doit pas reconstruire arbitrairement des objets et leurs comportements. Dans une nouvelle API, un format de données simple avec validation explicite limite les capacités accordées au parsing.
Erreur fréquente à comprendre Un chemin client absolu constitue une permission de lecture.
La sélection du fichier doit être contrôlée.
Un token décodé n’est pas une identité validée
Le payload d’un JWT peut être lisible sans que sa signature soit correcte. Avant d’utiliser sub ou role, appliquez le contrat du profil : preuve cryptographique, clé et algorithme admis, issuer, audience, types et règles temporelles nécessaires. Ces contrôles établissent une identité utilisable dans ce contexte. L’autorisation sur la ressource vient ensuite ; une signature valide ne donne pas une permission universelle.
Erreur fréquente à comprendre Un payload décodé suffit à accorder admin.
Le parsing n’établit pas l’authenticité.
Un upload consomme plusieurs budgets
La limite du formulaire ne contrôle pas à elle seule le proxy, PHP, le disque ou la décompression d’une archive. Définissez taille reçue, nombre d’entrées, volume extrait et temps de traitement. Vérifiez le contenu selon l’usage plutôt que le seul suffixe client. Le stockage et la restitution doivent aussi limiter exécution, chemins et accès inter-comptes. Un fichier accepté n’est pas automatiquement sûr dans tout contexte de distribution.
Erreur fréquente à comprendre Un type MIME client garantit un fichier sain et petit.
La déclaration ne vérifie ni contenu ni coût.
Distinguer CSRF et XSS
Le CSRF profite notamment de l’envoi automatique d’une identité par le navigateur ; une XSS exécute un script dans un contexte compromis. Un token CSRF ne neutralise pas ce script, et HttpOnly ne supprime pas toutes ses actions. Les protections doivent donc être combinées selon leurs propriétés. Testez une mutation avec token absent, puis un rendu de donnée hostile ; ce sont deux frontières et deux preuves différentes.
Erreur fréquente à comprendre Un token CSRF neutralise toute injection de script.
Une XSS peut compromettre le contexte de confiance.
Maîtriser les sorties HTTP serveur
Une URL HTTPS reçue d’un client n’est pas forcément une destination autorisée. Un serveur peut disposer d’accès internes que l’appelant ne devrait pas piloter. Résolvez des partenaires identifiés dans un périmètre maîtrisé et vérifiez redirections, résolution réseau, taille et durée. Une URL initiale correcte ne suffit pas si un saut suivant change le destinataire. Les tests doivent employer un laboratoire et des dépendances simulées.
Erreur fréquente à comprendre Toute URL HTTPS reçue est une destination sûre.
Le canal ne rend pas la destination autorisée.
Limiter l’abus sans inventer des droits
Un quota protège une ressource ou un coût selon une fenêtre et des dimensions de comptage. Il ne vérifie pas les permissions sur une réservation. Inversement, un acteur autorisé peut encore épuiser une opération coûteuse. Définissez ces deux décisions séparément et placez la limitation avant l’effet qu’elle veut borner. Un compteur seulement local ne démontre pas une limite globale si plusieurs instances traitent la charge.
Erreur fréquente à comprendre Un quota disponible donne des droits métier.
Le budget et l’autorisation sont distincts.
Limiter les conséquences d’un diagnostic
Les logs d’authentification ne doivent pas devenir un stock de tokens, cookies et mots de passe. Des catégories de refus, une version et une corrélation peuvent suffire à l’investigation. Pour un outil CLI, la protection des arguments a aussi plusieurs niveaux : escapeshellarg traite une frontière du shell, mais une valeur commençant par un tiret peut encore devenir une option pour le programme lancé. Préférez une API adaptée et une grammaire d’entrée limitée.
Erreur fréquente à comprendre Un log interne peut exposer tous les bearer tokens sans risque.
Une fuite de diagnostic peut créer une capacité d’usurpation.
Atelier guidé : lire, expliquer et vérifier le code
<?php
declare(strict_types=1);
$body = 'document de laboratoire';
$reference = hash('sha256', $body);
echo hash_equals($reference, hash('sha256', $body)) ? 'same' : 'different';
Le programme calcule deux fois une empreinte SHA-256 sur les mêmes octets puis compare les chaînes attendues. La sortie same établit cette égalité dans le petit scénario. Elle ne prouve pas l’auteur du document : aucune référence authentifiée ni clé de signature n’apparaît. Un adversaire capable de remplacer le fichier et la référence publique peut recalculer les deux.
Conservez une référence séparée dans le laboratoire et modifiez un octet du contenu pour observer le refus. Remplacez ensuite les deux valeurs ensemble et notez la limite de cette preuve d’intégrité. Pour une authenticité partagée, étudiez un MAC avec une clé protégée ; pour un droit de lecture, ajoutez une décision d’autorisation. Ces propriétés nécessitent des contrôles distincts même si elles utilisent toutes des chaînes en PHP.
Argument shell délimité et option du programme
Escapeshellarg traite une frontière du shell : il vise à conserver une valeur comme un argument. Le programme lancé interprète ensuite cet argument selon ses propres règles. Un nom commençant par un tiret peut donc être lu comme une option même s’il n’a pas créé un second argument shell.
Préférez une API de bibliothèque lorsqu’elle couvre le besoin. Sinon, évitez le shell avec une API de processus à arguments séparés, validez l’opérande et utilisez le séparateur -- seulement si l’outil le documente. Vérifiez aussi chemin résolu et répertoire autorisé : aucun mécanisme de quoting n’établit un droit sur un fichier.
Dans un laboratoire, remplacez l’outil par un programme qui affiche ses arguments factices, puis vérifiez la commande cible sans effets sensibles. Testez tiret initial, espaces et nom hors périmètre. La preuve recherchée est une interprétation conforme de l’opérande par le programme final, pas simplement une chaîne qui semble correctement entourée de guillemets.
Points de vigilance : TLS protège le transport, mais ne définit pas la grammaire des arguments du programme. L’échappement vise justement la frontière du shell ; l’autre problème est l’interprétation par l’outil. Le scénario ne fait intervenir aucune requête SQL. Un argument correctement délimité peut rester une option dangereuse. Préférer une API, sinon éviter le shell et contrôler les arguments.
Documentation de référence ↗
UUID et requête préparée : deux protections sans preuve de tenant
Un UUID peut réduire la facilité de deviner une référence. Une requête préparée protège la séparation entre structure SQL et valeurs. Aucun de ces mécanismes ne prouve que la facture trouvée appartient à l’acteur ou à son tenant.
La portée doit venir du contexte authentifié et autorisé côté serveur. Chargez la facture avec les critères de périmètre nécessaires ou appliquez une décision d’objet avant de transmettre son contenu. Un tenantId librement reçu dans la query ne remplace pas cette source de confiance.
Créez deux tenants et une facture de chacun, puis présentez l’UUID valide du premier avec la session du second. Vérifiez absence de corps confidentiel et absence d’effet. Gardez aussi le cas du propriétaire et celui de la ressource absente. Cette matrice distingue l’isolation réelle d’un endpoint qui paraît protégé seulement parce que ses identifiants sont longs ou que son SQL utilise des placeholders.
Points de vigilance : Un identifiant opaque ne remplace pas le contrôle du droit de lecture. Limiter le volume ne rend pas autorisée une seule lecture interdite. La portée doit venir d’un contexte authentifié et autorisé, pas d’un tenantId librement fourni. Une référence peut être connue par de nombreux canaux même si l’interface la masque.
Documentation de référence ↗
Exemple commenté
<?php
declare(strict_types=1);
$name = '<script>alert(1)</script>';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
Le navigateur reçoit une représentation textuelle des caractères spéciaux dans ce contexte HTML. La même fonction ne sécurise pas une requête SQL concaténée.
Exercice de mise en pratique
Établissez une matrice de tests pour consulter ou annuler une réservation d’un autre utilisateur.
Voir une proposition de correction
Créez deux acteurs et deux ressources. Vérifiez l’autorisation pour lecture et mutation, y compris avec un identifiant deviné. Ajoutez tests CSRF et sorties contenant des caractères HTML.
- Identifier le contexte exact.
- Ne pas confondre authentification et autorisation.
- Exclure les secrets des logs.
Contrôle de connaissances
Quel contrôle compare un mot de passe à un hash password_hash ?
Réponse expliquée : password_verify — L’API interprète le sel et les paramètres enregistrés.
Cours associé : Sécurité aux frontières de confiance ↗
Documentation de référence ↗ https://www.php.net/manual/en/security.php
Module 8 / 8 · 90 min
Autoload, tests et livraison reproductible
Produire un artefact vérifiable et diagnostiquer avant d’activer.
Comprendre le mécanisme
Composer résout les contraintes de dépendances et enregistre les versions choisies dans composer.lock. Pour une application, install réutilise ce verrou ; update le recalcule. L’autoload PSR-4 associe préfixes de namespaces et répertoires : le nom, la casse et le chemin doivent correspondre, surtout après passage sur Linux. Les extensions PHP sont des exigences de plateforme ; une bibliothèque Composer ne remplace pas un pilote PDO absent.
Raisonner et vérifier
Une suite de tests combine logique unitaire, intégration de persistance et parcours HTTP. Analyse statique et tests détectent des défauts différents. Construisez l’artefact depuis une révision identifiée, vérifiez les exigences de plateforme puis préparez les caches sur la cible. Une vérification HTTP du site reste nécessaire : le PHP du serveur web peut différer du CLI. Prévoyez une procédure de retour applicatif et une stratégie distincte pour la compatibilité des migrations.
Installer les versions réellement verrouillées
composer install réutilise normalement les versions résolues dans le lock existant ; composer update peut modifier cette résolution selon les contraintes. Le lock rend les dépendances plus reproductibles, mais ne prouve pas leur sécurité ni la présence des extensions natives. Conservez-le avec la release et vérifiez l’environnement cible. Une installation globale d’outil non suivie peut produire un autre résultat que la CI sur le même code.
Erreur fréquente à comprendre La même contrainte ouverte garantit toujours le même résultat futur.
La résolution peut évoluer sans version verrouillée.
Ce que l’analyse statique apporte
PHPStan examine les contrats et usages de types sans rejouer tous les parcours métier. Il peut détecter un appel incompatible que les tests n’ont jamais exécuté, mais ne prouve pas toutes les règles d’autorisation ou l’absence de vulnérabilité. Fixez version, niveau et chemins analysés. Complétez le rapport par des tests de comportement et une revue des frontières ; chaque technique doit annoncer le périmètre qu’elle vérifie réellement.
Erreur fréquente à comprendre PHPStan exécute tous les parcours métier.
L’analyse et l’exécution ont des périmètres différents.
Les refus sont des comportements à tester
Un parcours nominal ne démontre pas ce que le service fait sur une entrée invalide ou une permission insuffisante. Écrivez le refus attendu et l’effet qui doit rester absent. Pour un import, vérifiez qu’un fichier rejeté ne laisse pas d’écriture métier partielle. Les classes de test doivent représenter les distinctions du contrat, comme absence, mauvais type et objet tiers, plutôt qu’un nombre arbitraire d’exemples similaires.
Erreur fréquente à comprendre Tester seulement le succès démontre toute sécurité.
Les refus et non-effets doivent être exercés.
Faire remonter le résultat au bon appelant
Une CI se fonde notamment sur les codes de sortie des processus. Afficher un message d’erreur puis terminer avec un succès peut laisser passer une régression. Examinez les branches de shell qui ajoutent true ou ignorent une exception. Le contrôle doit produire un résultat exploitable par l’étape suivante. Un test utile introduit volontairement un défaut connu et vérifie que la pipeline refuse effectivement l’artefact.
Erreur fréquente à comprendre Ignorer les codes de retour rend le contrôle plus fiable.
Un contrôle non bloquant peut laisser passer une régression.
Identifier le contenu servi
Le nom d’un dossier ne suffit pas à identifier une release exacte. Associez l’artefact à un commit ou une empreinte et vérifiez que le serveur expose bien cette version après bascule. Une copie réussie peut encore laisser un lien current, un cache ou un processus sur l’ancien code. Le health check doit donc relier comportement attendu et identité de release, pas seulement constater qu’un serveur répond quelque chose.
Erreur fréquente à comprendre Un dossier modifié à la main est toujours reproductible.
Version et contenu doivent être traçables.
Comparer les runtimes CLI et web
Le binaire PHP du shell et celui du serveur web peuvent charger des configurations et extensions différentes. La présence de pdo_mysql dans le CLI ne démontre pas celle du processus qui traite HTTP. Vérifiez chaque contexte avec les outils de diagnostic adaptés et un accès protégé. Le DSN, le driver et l’environnement applicatif doivent aussi correspondre : l’erreur could not find driver ne se corrige pas par une migration SQL.
Erreur fréquente à comprendre Composer remplace toutes les extensions natives du serveur.
Les dépendances PHP et extensions ont des installations distinctes.
Choisir un contrôle de santé représentatif
Un fichier statique peut être servi alors que l’application ou sa base est indisponible. Définissez précisément la garantie du contrôle : démarrage du framework, version active et dépendances nécessaires au parcours testé. Évitez d’y déclencher une mutation réelle ou d’y publier des secrets. Un échec doit aider à localiser la frontière en cause, tandis qu’un succès doit rester limité à ce qui a réellement été observé.
Erreur fréquente à comprendre Un seul fichier statique prouve toutes les dépendances métier.
Le test doit correspondre aux garanties recherchées.
Revenir au code et reprendre les données
Un retour de code peut rétablir un ancien artefact, mais ne remet pas automatiquement la base dans son état précédent. Une migration ou de nouvelles écritures peuvent rendre cet ancien code incompatible. Concevez les changements de schéma avec une stratégie de compatibilité et de reprise. Testez le scénario de bascule et son retour sur des données représentatives ; un simple changement de lien symbolique ne prouve pas la restaurabilité.
Erreur fréquente à comprendre Changer current annule toute migration et donnée récente.
Code et données ont des cycles de reprise distincts.
Distribuer sans inclure les secrets
Les credentials de production doivent suivre un canal et un périmètre d’accès adaptés. Les copier dans une image ou un artefact public peut les rendre récupérables, même si une étape ultérieure les retire de la vue finale. Vérifiez aussi la casse des chemins PSR-4 : un fichier toléré sur un poste local peut ne plus être trouvé sous Linux. Le namespace, le nom de classe et le chemin doivent respecter le contrat d’autoload.
Erreur fréquente à comprendre Un build public doit contenir tous les credentials réels.
La distribution du code ne doit pas exposer ces capacités.
Démontrer une restauration utile
Une sauvegarde présente sur disque n’est pas encore une preuve de reprise. Restaurez-la dans un environnement isolé, mesurez le délai et vérifiez les invariants métier importants. Conservez les versions d’outils et les préconditions nécessaires. L’exercice doit aussi considérer les secrets anciens qui pourraient être réintroduits. Un rapport de succès du producteur de sauvegarde ne démontre pas à lui seul toutes ces propriétés chez le consommateur.
Erreur fréquente à comprendre Le seul nom du fichier prouve la restaurabilité.
Le résultat de reprise doit être observé.
Atelier guidé : lire, expliquer et vérifier le code
php vendor/bin/phpstan analyse || true
printf 'quality step finished
'
La commande de l’exercice enchaîne PHPStan avec une branche de secours qui retourne true. Si l’analyse échoue, cette branche peut faire terminer la séquence avec un statut de succès. Le texte affiché ensuite ne répare aucun type. Une pipeline qui regarde seulement le code de sortie peut donc considérer l’étape comme réussie malgré les diagnostics visibles.
Retirez ce masquage dans le laboratoire, introduisez un appel de méthode incompatible puis examinez le statut retourné au shell. Vérifiez ensuite que l’étape de livraison est réellement bloquée. Gardez une fixture nominale afin de distinguer un contrôle correctement discriminant d’un job qui échoue toujours. Conservez enfin la version et la configuration de PHPStan pour rendre la preuve reproductible entre le poste et la CI.
PSR-4 : namespace et casse du chemin en production
Mapping PSR-4 : "App\\": "src/"
Classe : App\Payment\AuthorizeHandler
Fichier : src/payment/AuthorizeHandler.php
Avec une correspondance App\ vers src/, les segments de namespace et le nom de classe déterminent le chemin PSR-4 attendu. App\Payment\AuthorizeHandler correspond à src/Payment/AuthorizeHandler.php avec la casse prescrite. Un répertoire payment peut sembler fonctionner sur un système de fichiers permissif et échouer sur Linux sensible à la casse.
Vérifiez déclaration, préfixe Composer et chemin réel. Le scénario exclut une classmap autoritative : ne choisissez donc pas une explication fondée sur un mode d’autoload différent de celui annoncé. Corrigez la casse dans le dépôt puis régénérez l’autoload selon le processus normal de build.
Si le système local masque un renommage de casse seule, utilisez un nom temporaire dans Git pour enregistrer la transition. Exécutez un chargement de la classe dans un environnement représentatif avant release. Évitez les require manuels ajoutés au hasard, qui masqueraient le contrat de nommage au lieu de le corriger.
Points de vigilance : L’autoload PSR-4 fonctionne sans framework. PSR-4 associe les noms qualifiés à des chemins, pas à un fichier monolithique. Les noms peuvent contenir des majuscules ; la correspondance de casse doit être respectée. Le chemin diffère par sa casse. Un environnement local permissif peut masquer cette erreur.
Documentation de référence ↗
Exemple commenté
composer install --no-dev --classmap-authoritative
composer check-platform-reqs --no-dev
php -r 'print_r(PDO::getAvailableDrivers());'
Ces commandes installent les dépendances verrouillées puis vérifient la plateforme réelle. La liste des pilotes concerne le PHP qui exécute cette commande, pas nécessairement celui du domaine.
Exercice de mise en pratique
Documentez une panne où les migrations CLI réussissent mais la page HTTP échoue après activation.
Voir une proposition de correction
Comparer versions et extensions web/CLI, vérifier le document root et lire les logs. Contrôler un fichier de révision public puis une page applicative. Restaurer l’ancienne version si le contrôle échoue.
- Construire la même révision que celle testée.
- Tester les chemins sensibles sur une plateforme proche de la cible.
- Séparer rollback du code et évolution des données.
Contrôle de connaissances
Quelle commande réutilise normalement les versions du lock existant ?
Réponse expliquée : composer install — install consomme le verrou de l’application.
Cours associé : Autoload, tests et livraison reproductible ↗
Documentation de référence ↗ https://getcomposer.org/doc/01-basic-usage.md