Interroger la base depuis un formulaire : requêtes préparées
Envoyer à MySQL une valeur saisie par l'utilisateur sans danger avec prepare et execute, les marqueurs ? et :nom, la recherche LIKE, la page de détail ?id=, le cas « aucun résultat », rowCount et lastInsertId.
1Situer
Le visiteur de Souk Atlas tape « argan » dans la barre de recherche, ou clique sur un produit pour voir sa fiche. Dans les deux cas, une valeur venue du navigateur doit entrer dans une requête SQL. C'est le moment le plus sensible d'un site dynamique : on le fait toujours avec une requête préparée.
2Comprendre
Une requête préparée : le SQL d'abord, les valeurs ensuite
Le visiteur cherche « argan ». Cette valeur vient du navigateur : on ne la colle pas dans le SQL.
1 / 5
Pourquoi on ne colle jamais une valeur dans le SQL
On pourrait écrire $pdo->query("SELECT * FROM produits WHERE nom LIKE '%$q%'"). Ça marche… jusqu’à ce que quelqu’un cherche huile d'argan : l’apostrophe ferme la chaîne SQL et la requête plante. Et un visiteur malveillant peut taper du SQL à la place d’un mot : c’est l’injection SQL, étudiée en détail à la leçon C.5.
$req=$pdo->prepare('SELECT nom, prix FROM produits WHERE prix <= ? AND stock > ?');
$req->execute([150, 0]); // les ? sont remplis dans l'ordre$produits=$req->fetchAll();
Avec des marqueurs nommés, l’ordre n’a plus d’importance et la requête se lit mieux :
$req=$pdo->prepare('SELECT * FROM clients WHERE ville_id = :ville AND date_inscription >= :depuis');
$req->execute(['ville'=>1, 'depuis'=>'2024-06-01']);
On peut aussi lier chaque valeur avec bindValue(':ville', 1, PDO::PARAM_INT) avant un execute() sans argument. C’est utile pour imposer un type, par exemple pour LIMIT :n.
Afficher les résultats
Le schéma est presque toujours le même : lire les paramètres, préparer, exécuter, puis boucler sur les lignes ou traiter le cas « rien trouvé ».
<?php$req=$pdo->prepare('SELECT id, nom, prix FROM produits WHERE nom LIKE ?');
$req->execute(["%$q%"]);
$produits=$req->fetchAll();
?><?phpif (!$produits):?><p>Aucun produit pour « <?=htmlspecialchars($q) ?> ».</p><?phpelse:?><ul><?phpforeach ($produitsas$p):?><li><ahref="produit.php?id=<?=$p['id'] ?>"><?=htmlspecialchars($p['nom']) ?></a></li><?phpendforeach; ?></ul><?phpendif; ?>
Un tableau vide est faux pour if, d’où if (!$produits). Et toute valeur affichée passe par htmlspecialchars, même si elle vient de votre base : un nom de produit peut contenir < ou &.
La page de détail
Après une écriture : rowCount et lastInsertId
Les requêtes préparées servent aussi pour INSERT, UPDATE et DELETE (leçon C.3). Deux méthodes renseignent sur ce qui s’est passé :
$req->rowCount() : le nombre de lignes modifiées, ajoutées ou supprimées ;
$pdo->lastInsertId() : l’identifiant AUTO_INCREMENT que MySQL vient d’attribuer.
$req=$pdo->prepare('INSERT INTO villes (nom, region) VALUES (?, ?)');
$req->execute(['Meknès', 'Fès-Meknès']);
echo"Ville n° ".$pdo->lastInsertId(); // Ville n° 7
Exemple simple
Le formulaire à trous
La banque vous tend un chèque déjà imprimé avec des cases vides : vous ne pouvez écrire que dans les cases, pas modifier le texte imprimé. Une requête préparée, c'est le chèque : le SQL est fixé d'avance, l'utilisateur ne remplit que les valeurs.
Exemple simple
La fiche produit
Sur un site marchand, l'adresse produit.php?id=9 affiche le tapis berbère. Le même fichier sert pour tous les produits : seul l'identifiant change, et c'est la base qui fournit le reste.
Publicité
3Manipuler
Terminez la recherche du catalogue et la page de détail. Les deux reçoivent une valeur du navigateur : utilisez des requêtes préparées.
Pas à pas dans le lab ↗Cliquez sur Exécuter : PHP 8.3 démarre dans votre navigateur (environ 5 Mo à télécharger, une seule fois).
La page s'affichera ici.
Le navigateur demandera /recherche.php au serveur, qui exécutera le PHP et renverra du HTML.
Entre le navigateur et le serveuraucun échange pour l'instant
Consignes 0 / 4
Chercher « argan » trouve l'huile d'argan, avec une requête préparée
Chercher « caftan » affiche « Aucun produit »
produit.php?id=9 affiche Tapis berbère, Textile et 1 200,00 DH
produit.php?id=99 répond 404 « Produit introuvable »
4Pratiquer
Exercice 1 · Filtrer par prix et catégorie
Ajoutez au formulaire une liste déroulante des catégories (lue dans la base) et un prix maximum. La requête combine les deux avec des marqueurs nommés.
Voir le corrigé
<?phprequire __DIR__ .'/db.php';
$cats=$pdo->query('SELECT id, nom FROM categories ORDER BY nom')->fetchAll();
$cat= (int) ($_GET['cat'] ??0);
$max= (float) ($_GET['max'] ??10000);
$req=$pdo->prepare('SELECT nom, prix FROM produits
WHERE categorie_id = :cat AND prix <= :max ORDER BY prix');
$req->execute(['cat'=>$cat, 'max'=>$max]);
$produits=$req->fetchAll();
?><form><selectname="cat"><?phpforeach ($catsas$c):?><optionvalue="<?=$c['id'] ?>" <?=$c['id'] ==$cat?'selected':''?>><?=htmlspecialchars($c['nom']) ?></option><?phpendforeach; ?></select><inputtype="number"name="max"value="<?=$max?>">
<button>Filtrer</button></form>
Exercice 2 · Les commandes d'un client
Une page commandes.php?client=1 affiche le nom du client puis ses commandes (date, statut, montant total calculé en SQL avec SUM et GROUP BY).
Voir le corrigé
<?phprequire __DIR__ .'/db.php';
$id= (int) ($_GET['client'] ??0);
$req=$pdo->prepare('SELECT c.id, c.date_commande, c.statut,
SUM(l.quantite * l.prix_unitaire) AS total
FROM commandes c JOIN lignes_commande l ON l.commande_id = c.id
WHERE c.client_id = ?
GROUP BY c.id, c.date_commande, c.statut
ORDER BY c.date_commande');
$req->execute([$id]);
foreach ($req->fetchAll() as$cmd) {
echo"Commande n° {$cmd['id']} du {$cmd['date_commande']} ({$cmd['statut']}) : {$cmd['total']} DH<br>";
}