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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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.
Understand this common mistake
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é.
Reference documentation ↗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.
Reference documentation ↗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.
Reference documentation ↗Worked example
<?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.
Practical exercise
Ajoutez une clé de requête unique pour empêcher un double traitement de la même réservation.
View a suggested solution
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.
Verification checklist
- Lier les valeurs, autoriser les identifiants.
- Écrire les invariants dans des contraintes ou mises à jour atomiques.
- Tester l’échec et le rollback.
Knowledge check
Related lesson: PDO, SQL et cohérence transactionnelle ↗Module validation
101 questions, local practice target of 75%. Suggested duration of 150 minutes, with no enforced deadline. Questions and choices are shuffled; corrections appear after submission. The quick lesson check and the final course exam remain separate exercises.
Start or resume the 101 questions →This records your own progress and does not certify mastery. Complete the exercise before moving on.
