PHP MySQL Prepared Statements : Guide complet
Les prepared statements renforcent la sécurité et les performances des applications PHP avec MySQL. Découvrez MySQLi et PDO avec des exemples.
Les prepared statements sont la technique la plus importante pour écrire du code PHP sécurisé qui interagit avec une base de données. Ils séparent la commande SQL des données qu'elle manipule, ce qui empêche totalement les injections SQL et accélère également les requêtes exécutées de façon répétée. Ce guide explique ce que sont les prepared statements, pourquoi ils sont importants, et comment les utiliser avec les extensions MySQLi et PDO.
Cette page couvre :
- Ce qu'est un prepared statement et pourquoi le modèle « compiler une fois, exécuter plusieurs fois » existe
- Écrire des requêtes
INSERTetSELECTpréparées avec MySQLi - Les mêmes patterns avec PDO (placeholders nommés)
- Les erreurs courantes : rapports d'erreurs, liaison du mauvais type, et réutilisation des statements
Que sont les Prepared Statements ?
Un prepared statement est une requête SQL envoyée à la base de données en deux étapes :
- Préparer — vous envoyez le SQL avec des placeholders
?(ou:nom) à la place des vraies valeurs. La base de données parse, compile et optimise ce modèle une seule fois. - Exécuter — vous envoyez les valeurs réelles séparément. La base de données les insère dans le plan déjà compilé et l'exécute.
Puisque les valeurs transitent sur un canal différent du texte SQL, la base de données ne confond jamais données et commandes. Une valeur comme ' OR '1'='1 est traitée comme une chaîne de caractères littérale à rechercher, et non comme du SQL à exécuter — c'est précisément pourquoi les attaques par injection échouent contre les prepared statements.
Pourquoi utiliser les Prepared Statements ?
- Sécurité. Les saisies utilisateur ne peuvent jamais modifier la structure de votre requête. C'est la défense recommandée contre les injections SQL, et la raison pour laquelle vous ne devriez jamais construire des requêtes en concaténant des variables dans une chaîne.
- Performances. La requête est parsée et compilée une seule fois. Si vous l'exécutez plusieurs fois (par exemple pour insérer 1 000 lignes dans une boucle), la base de données réutilise le même plan au lieu de tout reparser à chaque fois.
- Code plus propre. Les placeholders éliminent l'échappement manuel avec
mysqli_real_escape_string()et la gestion des guillemets. Vous liez une variable et c'est terminé.
Règle d'or : dès qu'une partie d'une requête provient d'une saisie utilisateur — un champ de formulaire, un paramètre d'URL, un cookie — utilisez un prepared statement.
Les étapes
Chaque prepared statement suit le même cycle de vie :
- Se connecter à la base de données.
- Préparer le SQL avec des placeholders.
- Lier vos variables aux placeholders.
- Exécuter le statement.
- Récupérer les résultats (pour les requêtes
SELECT). - Fermer le statement.
INSERT préparé avec MySQLi
MySQLi utilise des placeholders positionnels ?. Vous les liez avec mysqli_stmt_bind_param(), où le premier argument est une chaîne de types : s pour string, i pour integer, d pour double/float, b pour blob.
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // throw on errors
$conn = mysqli_connect("localhost", "username", "password", "database");
$stmt = mysqli_prepare($conn, "INSERT INTO users (name, email) VALUES (?, ?)");
// "ss" => both placeholders are strings, in order
mysqli_stmt_bind_param($stmt, "ss", $name, $email);
$name = "John";
$email = "[email protected]";
mysqli_stmt_execute($stmt); // inserts John
$name = "Jane";
$email = "[email protected]";
mysqli_stmt_execute($stmt); // reuses the same compiled statement, inserts Jane
mysqli_stmt_close($stmt);
mysqli_close($conn);bind_param lie par référence, donc vous pouvez modifier $name/$email et appeler execute() à nouveau sans relier — les nouvelles valeurs sont automatiquement prises en compte. C'est l'avantage du modèle « compiler une fois, exécuter plusieurs fois » en pratique.
SELECT préparé avec MySQLi
Pour un SELECT, vous exécutez le statement puis lisez les lignes. La méthode la plus simple est mysqli_stmt_get_result(), qui vous donne un jeu de résultats normal sur lequel vous pouvez itérer :
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$conn = mysqli_connect("localhost", "username", "password", "database");
$stmt = mysqli_prepare($conn, "SELECT id, name FROM users WHERE email = ?");
mysqli_stmt_bind_param($stmt, "s", $email);
$email = "[email protected]";
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
echo $row["id"] . ": " . $row["name"] . "\n";
}
mysqli_stmt_close($stmt);
mysqli_close($conn);Prepared Statements avec PDO
PDO est l'autre extension de base de données courante, et de nombreux développeurs la préfèrent car elle fonctionne avec différents systèmes de bases de données et prend en charge les placeholders nommés (:email), qui sont plus lisibles.
<?php
$pdo = new PDO(
"mysql:host=localhost;dbname=database;charset=utf8mb4",
"username",
"password",
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
// INSERT with named placeholders
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
$stmt->execute([":name" => "John", ":email" => "[email protected]"]);
// SELECT and fetch
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE email = :email");
$stmt->execute([":email" => "[email protected]"]);
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo $row["id"] . ": " . $row["name"] . "\n";
}Notez que vous n'avez pas à déclarer les types avec PDO — vous passez un tableau associatif de valeurs directement à execute(). Définir PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION fait lever une exception à PDO en cas d'échec, de sorte que les problèmes ne sont jamais ignorés silencieusement.
Erreurs courantes
- Oublier le rapport d'erreurs. Par défaut, MySQLi peut échouer silencieusement. Appelez
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT)(ou utilisez les exceptions PDO) pour qu'une mauvaise requête lève une exception au lieu de retournerfalse. - Lier le mauvais nombre de types. La chaîne de types dans
bind_paramdoit avoir exactement un caractère par?."ss"pour deux placeholders,"si"pour un string puis un integer. - Placer un placeholder là où SQL ne le permet pas. Vous pouvez lier des valeurs, pas des identifiants.
WHERE id = ?fonctionne ;ORDER BY ?ouSELECT * FROM ?ne fonctionne pas — les noms de tables et de colonnes doivent être écrits en dur ou validés par liste blanche. - Concaténer « juste cette valeur ». Il n'y a pas d'exception sûre. Si elle vient d'un utilisateur, liez-la.
Conclusion
Les prepared statements divisent une requête en un modèle SQL compilé et les valeurs qui le complètent. Cette séparation est ce qui les rend à la fois sûrs (résistants aux injections) et rapides (parsés une fois, exécutés plusieurs fois). Utilisez les placeholders ? de MySQLi ou les placeholders nommés :valeur de PDO, mais liez toujours les saisies utilisateur plutôt que de construire le SQL manuellement.
Continuez avec les chapitres connexes : Connexion à MySQL, Insertion de données, Sélection de données, et la référence mysqli_prepare().