3.1MySQL & PDO : connexion et requêtes préparées
Une application web réelle STOCKE des données de façon durable : utilisateurs, produits, commandes, articles. C'est le rôle d'une base de données. En PHP, la base la plus utilisée est MySQL (ou son équivalent MariaDB), une base RELATIONNELLE : les données sont organisées en tables (comme des feuilles de tableur, avec des colonnes typées), les enregistrements en lignes, et on interroge la base avec le langage SQL (SELECT pour lire, INSERT pour créer, UPDATE pour modifier, DELETE pour supprimer — les quatre opérations du CRUD). Pour se CONNECTER à MySQL depuis PHP et exécuter des requêtes, l'approche moderne et recommandée est PDO (PHP Data Objects) : une interface unifiée et sûre pour les bases de données. (Une autre extension, MySQLi, existe et est spécifique à MySQL ; PDO a l'avantage de fonctionner avec plusieurs types de bases — une abstraction utile.) On se connecte en créant un objet PDO (avec l'hôte, le nom de la base, l'utilisateur, le mot de passe), puis on exécute des requêtes.
Le point ABSOLUMENT CENTRAL de cette section — le plus important pour la sécurité — est l'usage des requêtes préparées. Le danger : si vous construisez une requête SQL en CONCATÉNANT directement des données utilisateur dedans (par exemple 'SELECT * FROM users WHERE email = ' . $email), vous créez une faille critique d'injection SQL — un attaquant peut injecter du code SQL malveillant via les données, et LIRE, MODIFIER ou DÉTRUIRE votre base. C'est l'une des failles web les plus graves et les plus classiques. La solution est la requête préparée : au lieu de mettre les données directement dans la requête, on utilise des marqueurs (placeholders) et on passe les données SÉPARÉMENT. Avec PDO : on PRÉPARE la requête avec des marqueurs (? ou nommés :email) — $stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email'); — puis on l'EXÉCUTE en passant les données ($stmt->execute(['email' => $email]);) et on récupère les résultats ($stmt->fetch() pour une ligne, $stmt->fetchAll() pour toutes). La base traite alors les données comme des DONNÉES pures (jamais comme du code SQL), quelle que soit leur contenu — l'injection SQL devient impossible. La règle est absolue : toute requête impliquant des données variables (utilisateur ou autre) DOIT être préparée ; ne JAMAIS concaténer de données dans une requête SQL. C'est non négociable. Réaliser un CRUD complet (créer, lire, modifier, supprimer des enregistrements) avec PDO et des requêtes préparées — se connecter, préparer, exécuter, récupérer les résultats, gérer les erreurs — est la compétence centrale du PHP « avec base de données ». C'est ce qui transforme un site statique en application réelle qui STOCKE et manipule des données de façon durable et SÉCURISÉE. Maîtriser PDO et les requêtes préparées (sécurité) est fondamental — la persistance des données est au cœur de toute application web sérieuse.
Vocabulaire de la section
- Base de données / MySQL
- Système stockant durablement les données (utilisateurs, produits…). MySQL/MariaDB : base RELATIONNELLE (tables, lignes, colonnes) interrogée en SQL (SELECT/INSERT/UPDATE/DELETE = CRUD).
- PDO
- PHP Data Objects : interface moderne, unifiée et sûre pour se connecter aux bases et exécuter des requêtes ; fonctionne avec plusieurs types de bases (abstraction).
- Injection SQL
- Faille CRITIQUE : concaténer des données utilisateur dans une requête SQL permet à un attaquant d'injecter du SQL malveillant (lire/modifier/détruire la base). À éviter absolument.
- Requête préparée
- Requête avec des MARQUEURS (? ou :nom) où les données sont passées SÉPARÉMENT (prepare puis execute) ; la base les traite comme données pures — l'injection SQL devient impossible.
- CRUD
- Les quatre opérations de base : Create (INSERT), Read (SELECT), Update (UPDATE), Delete (DELETE) ; réalisées avec PDO et des requêtes préparées, avec récupération (fetch/fetchAll).
Comment se protéger de l'INJECTION SQL en PHP ?
En pratique — Persister des données avec MySQL et PDO
- Créez une base et une table dans MySQL (via phpMyAdmin) ; connectez-vous depuis PHP avec PDO (hôte, base, utilisateur, mot de passe).
- Réalisez un CRUD avec des REQUÊTES PRÉPARÉES : INSERT (créer), SELECT (lire, fetch/fetchAll), UPDATE (modifier), DELETE (supprimer).
- Utilisez systématiquement des marqueurs (:nom ou ?) et passez les données via execute() — JAMAIS de concaténation de données dans le SQL.
- Gérez les erreurs (PDO peut lever des exceptions) et vérifiez qu'une injection SQL est impossible avec les requêtes préparées.
Points clés à retenir
- Une application réelle STOCKE ses données dans une BASE. MySQL/MariaDB (relationnelle : tables, lignes) interrogée en SQL (SELECT/INSERT/UPDATE/DELETE = CRUD).
- PDO (PHP Data Objects) = l'interface MODERNE et sûre pour se connecter et exécuter des requêtes (fonctionne avec plusieurs bases).
- CENTRAL : les REQUÊTES PRÉPARÉES. Concaténer des données dans une requête = faille CRITIQUE d'INJECTION SQL. Utiliser des MARQUEURS (:nom / ?) et passer les données SÉPARÉMENT (prepare + execute).
- RÈGLE ABSOLUE : toute requête avec des données variables DOIT être préparée ; ne JAMAIS concaténer. Réaliser un CRUD complet (fetch/fetchAll) avec PDO et requêtes préparées = compétence centrale.
Questions fréquentes
Qu'est-ce que l'injection SQL, et pourquoi les requêtes préparées sont-elles la solution ?
L'injection SQL est l'une des failles de sécurité les plus graves et classiques du web, et les requêtes préparées sont LA protection fondamentale — comprendre les deux est essentiel pour tout développeur qui touche à une base de données (nous l'avons vu pour Node, c'est identique en principe pour PHP). Ce qu'est l'injection SQL : c'est une attaque où un attaquant INJECTE du code SQL malveillant via des données qu'il envoie (un champ de formulaire, un paramètre d'URL), et où ce code se retrouve EXÉCUTÉ par votre base. Cela arrive quand vous construisez une requête en CONCATÉNANT directement des données utilisateur. Exemple du DANGER (à NE PAS faire) : imaginez une recherche d'utilisateur construite ainsi en concaténant : la requête SELECT * FROM users WHERE email = suivie directement de la valeur saisie. Si l'attaquant saisit une valeur contenant du SQL (comme une chaîne conçue pour toujours être vraie, ou pour ajouter une commande), il peut : contourner la logique (par exemple, se connecter sans mot de passe si la condition est manipulée), LIRE toute la base (extraire tous les utilisateurs, mots de passe, données), la MODIFIER, ou la DÉTRUIRE (une injection peut contenir une commande de suppression de table). Les conséquences sont catastrophiques : vol de données personnelles et de mots de passe, contournement d'authentification, destruction de données, compromission complète. L'injection SQL figure régulièrement en tête des failles web les plus critiques. La cause profonde : MÉLANGER du CODE (la requête SQL) et des DONNÉES (les entrées utilisateur) par concaténation, de sorte que des données puissent être interprétées comme du code SQL. La SOLUTION — les requêtes préparées : au lieu de mettre les données DANS la requête (concaténation), on sépare le CODE (la requête, avec des MARQUEURS aux emplacements des données) et les DONNÉES (passées séparément). Le processus en deux temps : (1) on PRÉPARE la requête avec des marqueurs (? positionnels ou :nom nommés) — la structure de la requête est fixée, avec des « trous » pour les données ; (2) on EXÉCUTE en passant les données pour ces marqueurs. Pourquoi c'est SÛR : la base reçoit d'abord la STRUCTURE de la requête (le code SQL, avec les marqueurs), puis les DONNÉES séparément. Elle traite alors les données comme des DONNÉES PURES — de simples valeurs à insérer aux emplacements des marqueurs — JAMAIS comme du code SQL à exécuter, quelle que soit leur contenu. Même si un attaquant met du SQL malveillant dans une donnée, ce SQL ne sera PAS exécuté (il sera traité comme une simple chaîne de caractères). L'injection SQL devient IMPOSSIBLE. C'est une séparation nette entre code et données qui élimine la faille à la racine. Avec PDO : (1) $stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email'); (préparer avec un marqueur nommé) ; (2) $stmt->execute(['email' => $email]); (exécuter en passant la donnée) ; (3) récupérer les résultats ($stmt->fetch() ou fetchAll()). La donnée $email est passée séparément, traitée comme une valeur pure — sûre, quelle que soit sa contenu. La RÈGLE ABSOLUE : toute requête impliquant des données VARIABLES (venant de l'utilisateur ou de n'importe quelle source) DOIT être une requête préparée. Ne JAMAIS concaténer de données dans une requête SQL. C'est non négociable — concaténer des données dans du SQL crée une faille critique, quasi certainement exploitable. Précisions importantes : (1) Les requêtes préparées protègent les DONNÉES (les valeurs), pas les IDENTIFIANTS SQL (noms de tables, de colonnes) qui ne peuvent pas être des marqueurs — si un nom de table/colonne doit être variable (rare), utilisez une LISTE BLANCHE de valeurs autorisées (jamais une donnée utilisateur directe). (2) Les requêtes préparées ont aussi un bénéfice de PERFORMANCE quand on exécute plusieurs fois la même requête (elle est « préparée » une fois). (3) Ne vous fiez PAS à l'échappement manuel (des fonctions d'échappement existent mais sont piégeuses) — les requêtes préparées sont la méthode SÛRE et recommandée. (4) Les ORM (comme ceux des frameworks, Eloquent de Laravel, Doctrine de Symfony — section 4-2/4-3) utilisent les requêtes préparées AUTOMATIQUEMENT sous le capot — un avantage de sécurité (en les utilisant normalement, vous êtes protégé). Le principe général : ne JAMAIS faire confiance aux entrées, ne jamais les mêler à du code (SQL, HTML, commandes) sans précaution. L'injection SQL est un cas de ce principe ; il existe des injections similaires ailleurs (NoSQL, commandes système) — la même vigilance s'applique. En résumé : l'injection SQL est une faille critique où un attaquant injecte du SQL malveillant via des données concaténées dans une requête (vol/destruction de données, contournement d'auth). Les requêtes préparées sont LA solution : elles SÉPARENT le code SQL (avec des marqueurs) et les données (passées séparément, traitées comme valeurs pures), rendant l'injection impossible. La règle absolue : toujours des requêtes préparées pour les données variables, JAMAIS de concaténation. C'est un savoir de sécurité fondamental et non négociable — l'appliquer systématiquement protège votre base et les données de vos utilisateurs. Une faille d'injection SQL peut ruiner une application ; les requêtes préparées l'éliminent.
Pourquoi utiliser PDO plutôt qu'une autre méthode, et comment structurer l'accès à la base ?
PDO est l'approche moderne et recommandée pour accéder aux bases de données en PHP, et bien structurer cet accès (au-delà de la simple connexion) rend le code plus propre, sûr et maintenable — voici pourquoi PDO et comment l'organiser. Pourquoi PDO : PHP offre plusieurs façons d'accéder à MySQL : les anciennes fonctions mysql_* (OBSOLÈTES, supprimées, à ne JAMAIS utiliser), MySQLi (spécifique à MySQL), et PDO (PHP Data Objects). PDO est privilégié pour plusieurs raisons : (1) ABSTRACTION multi-bases. PDO fournit une interface UNIFIÉE qui fonctionne avec DIFFÉRENTS types de bases (MySQL, PostgreSQL, SQLite…). Si vous changez de base, votre code PDO change peu (contrairement à MySQLi, lié à MySQL). Cette portabilité est un atout. (2) REQUÊTES PRÉPARÉES propres. PDO gère élégamment les requêtes préparées (avec marqueurs nommés :nom, plus lisibles que les positionnels) — la sécurité est facile à bien faire. (3) Orienté OBJET. PDO a une API objet cohérente et moderne. (4) Gestion d'ERREURS par EXCEPTIONS. PDO peut être configuré pour lever des EXCEPTIONS en cas d'erreur (mode recommandé : PDO::ERRMODE_EXCEPTION), ce qui permet une gestion d'erreurs propre (try/catch) plutôt que de vérifier des codes de retour. (5) Fonctionnalités : récupération flexible des résultats (en tableaux associatifs, en objets…), transactions, etc. Pour ces raisons, PDO est le choix moderne recommandé (MySQLi reste valable si on ne cible que MySQL, mais PDO est plus polyvalent). Comment structurer l'accès à la base : (1) CENTRALISEZ la connexion. Ne créez pas une connexion PDO dans chaque fichier (duplication, difficile à maintenir). Créez la connexion à UN endroit (un fichier de configuration/connexion, ou mieux une classe/service), et réutilisez-la. Cela facilite les changements (identifiants, configuration) et évite les connexions multiples. (2) Configurez PDO correctement : mode d'erreur en EXCEPTIONS (pour attraper les erreurs proprement), mode de récupération par défaut (tableaux associatifs souvent), encodage UTF-8 (pour gérer les accents correctement) — ces réglages à la création de la connexion. (3) Gérez les IDENTIFIANTS de connexion en SÉCURITÉ. Les identifiants de la base (hôte, nom, utilisateur, mot de passe) ne doivent PAS être codés en dur dans le code versionné ni exposés — utilisez des variables d'environnement ou un fichier de configuration HORS du dépôt Git (comme les .env). Ne mettez JAMAIS le mot de passe de la base dans un fichier public ou versionné. (4) Utilisez TOUJOURS des requêtes préparées pour les données variables (sécurité — voir FAQ précédente). (5) Gérez les ERREURS : entourez les opérations de base d'un try/catch (PDO lève des exceptions en cas de problème) pour gérer proprement les erreurs (message, log) sans exposer de détails sensibles à l'utilisateur en production. (6) Séparez l'accès aux données de la logique. Une bonne pratique (qui mène vers le pattern Repository/Modèle du MVC, section 4-1) est de regrouper les opérations de base dans des fonctions/classes DÉDIÉES (par exemple, une classe qui gère les utilisateurs avec des méthodes trouverParEmail, creer…) plutôt que d'éparpiller des requêtes SQL partout dans le code. Cela rend le code plus organisé, testable, et maintenable (les requêtes sont à un endroit). (7) Utilisez les TRANSACTIONS quand plusieurs opérations doivent réussir ou échouer ENSEMBLE (par exemple, débiter un compte ET créditer un autre) — PDO gère les transactions (begin/commit/rollback) pour garantir la cohérence. L'évolution vers les ORM : quand vous utiliserez des frameworks (niveau 4), vous emploierez souvent un ORM (Eloquent de Laravel, Doctrine de Symfony) qui construit PAR-DESSUS PDO (ou équivalent) une couche plus haute : vous manipulez des OBJETS et des méthodes au lieu d'écrire du SQL, avec les requêtes préparées automatiques (sécurité), la gestion des relations, les migrations. Les ORM sont productifs et sûrs — mais comprendre PDO et le SQL sous-jacent reste important (pour comprendre ce que fait l'ORM, déboguer, et pour les requêtes pointues). En résumé : utilisez PDO (l'approche moderne : abstraction multi-bases, requêtes préparées propres, orienté objet, gestion d'erreurs par exceptions) plutôt que les anciennes fonctions. Structurez l'accès en CENTRALISANT la connexion (un endroit, configurée correctement — mode exception, UTF-8), en GÉRANT les identifiants en sécurité (hors dépôt), en utilisant TOUJOURS des requêtes préparées, en gérant les erreurs (try/catch), en SÉPARANT l'accès aux données de la logique (fonctions/classes dédiées, vers le pattern Repository), et en utilisant les transactions quand nécessaire. Cette structuration prépare le pattern MVC et l'usage des ORM. Un accès à la base bien structuré (PDO, connexion centralisée, requêtes préparées, séparation) est propre, sûr et maintenable — le socle de toute application PHP avec base de données.