Chapitre 727 minIntermédiaire
PDO : connexion à MySQL et requêtes préparées
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- se connecter à MySQL ou MariaDB avec PDO et les bonnes options ;
- écrire des requêtes préparées avec marqueurs nommés ou positionnels ;
- traiter les cas LIMIT, IN, LIKE et tri choisi par l'utilisateur ;
- expliquer une injection SQL et la corriger.
4 exercices corrigés2 schémas
PDO (PHP Data Objects) est l'extension de PHP qui permet à un script de se connecter à une base de données et d'y exécuter des requêtes SQL. Avec PDO, une requête préparée envoie le texte de la requête et les valeurs séparément : c'est la protection contre l'injection SQL. Ce chapitre présente la base de Prêtmat, montre comment se connecter à MySQL ou MariaDB avec les bonnes options, comment écrire des requêtes préparées dans tous les cas courants, et comment fonctionne l'injection que ces requêtes empêchent. La lecture des résultats et les transactions sont au chapitre 8.
Ce chapitre suppose que tu sais écrire une requête SQL simple ; sinon, commence par le cours SQL.
PDO, mysqli et l'ancienne extension mysql
| Extension | SGBD | Style | Statut |
|---|---|---|---|
mysql_* (mysql_connect(), mysql_query()) | MySQL | Fonctions | Supprimée depuis PHP 7.0 (2015) : un tutoriel qui l'utilise est périmé |
mysqli | MySQL et MariaDB seulement | Fonctions ou objet | Maintenue |
| PDO | MySQL, MariaDB, PostgreSQL, SQLite, SQL Server, Oracle… | Objet | Maintenue, la plus utilisée |
PDO et mysqli sont tous deux valables. Ce cours utilise PDO : la même interface sert pour tous les SGBD, les marqueurs nommés (:reference) rendent les requêtes lisibles, et les erreurs sont des exceptions par défaut depuis PHP 8.0. Chaque SGBD a son pilote : pdo_mysql pour MySQL et MariaDB, à activer dans php.ini ou à installer dans l'image Docker (chapitre 1). La commande php -m doit lister PDO et pdo_mysql.
La base de Prêtmat
L'application repose sur trois tables : les utilisateurs, le matériel et les emprunts.
utilisateur (id, email, nom, mot_de_passe, role, cree_le)
materiel (id, reference, libelle, categorie, etat, date_achat, valeur, photo)
emprunt (id, #id_materiel, #id_utilisateur, date_debut, date_retour_prevue, date_retour)
Un matériel est prêté quand il a un emprunt dont date_retour est NULL. La colonne etat décrit l'objet lui-même : disponible, en réparation ou réformé (sorti de l'inventaire). Voici le script de création, à placer dans sql/pretmat.sql (le dossier lu par le conteneur MySQL au premier démarrage) ou à exécuter dans phpMyAdmin :
CREATE DATABASE IF NOT EXISTS pretmat CHARACTER SET utf8mb4;
USE pretmat;
CREATE TABLE utilisateur (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(180) NOT NULL UNIQUE,
nom VARCHAR(100) NOT NULL,
mot_de_passe VARCHAR(255) NOT NULL,
role ENUM('employe', 'gestionnaire') NOT NULL DEFAULT 'employe',
cree_le DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE materiel (
id INT AUTO_INCREMENT PRIMARY KEY,
reference VARCHAR(20) NOT NULL UNIQUE,
libelle VARCHAR(100) NOT NULL,
categorie ENUM('portable', 'videoprojecteur', 'tablette', 'camera', 'autre') NOT NULL,
etat ENUM('disponible', 'en_reparation', 'reforme') NOT NULL DEFAULT 'disponible',
date_achat DATE NULL,
valeur DECIMAL(8, 2) NULL,
photo VARCHAR(100) NULL
);
CREATE TABLE emprunt (
id INT AUTO_INCREMENT PRIMARY KEY,
id_materiel INT NOT NULL,
id_utilisateur INT NOT NULL,
date_debut DATE NOT NULL,
date_retour_prevue DATE NOT NULL,
date_retour DATE NULL,
CONSTRAINT fk_emprunt_materiel FOREIGN KEY (id_materiel) REFERENCES materiel (id),
CONSTRAINT fk_emprunt_utilisateur FOREIGN KEY (id_utilisateur) REFERENCES utilisateur (id),
CONSTRAINT ck_emprunt_dates CHECK (date_retour_prevue >= date_debut)
);
-- Les trois comptes ont le mot de passe de démonstration Pretmat-Demo-2026
INSERT INTO utilisateur (email, nom, mot_de_passe, role) VALUES
('chloe.lambert@sorbier.example', 'Chloé Lambert', '$2y$12$dpTJJvYd0OCLxite6rMBU.ixlTye32XPRA0NIxeGZlUbyDyQSfayK', 'gestionnaire'),
('julie.lefevre@sorbier.example', 'Julie Lefèvre', '$2y$12$11oadVkqdytcwgs/rtV1AOcorQgImIe0YEMURuWPUCcBUcUqlMXgy', 'employe'),
('karim.morel@sorbier.example', 'Karim Morel', '$2y$12$MuGuMGy4EyFTfaULtOb2oeBHG8XB6dN9rEKMYEu1m.jhq3yTKIRDG', 'employe');
INSERT INTO materiel (reference, libelle, categorie, etat, date_achat, valeur) VALUES
('PC-0101', 'Portable Dell Latitude 5450', 'portable', 'disponible', '2025-09-15', 1180.00),
('PC-0102', 'Portable Dell Latitude 5450', 'portable', 'disponible', '2025-09-15', 1180.00),
('VP-0001', 'Vidéoprojecteur Epson EB-L210W', 'videoprojecteur', 'disponible', '2024-03-02', 899.00),
('TAB-0003', 'Tablette Samsung Galaxy Tab S9 FE', 'tablette', 'en_reparation', '2024-11-20', 449.90),
('CAM-0001', 'Caméra de visioconférence Logitech', 'camera', 'disponible', '2025-01-10', 329.00),
('PC-0087', 'Portable HP ProBook 450 G9', 'portable', 'reforme', '2021-06-10', 899.00);
INSERT INTO emprunt (id_materiel, id_utilisateur, date_debut, date_retour_prevue, date_retour) VALUES
(2, 3, '2026-10-26', '2026-11-09', NULL),
(3, 2, '2026-10-20', '2026-10-22', '2026-10-22'),
(5, 2, '2026-10-28', '2026-10-30', NULL);
Le 2 novembre 2026, date des exemples du cours, Karim a le portable PC-0102 jusqu'au 9 novembre, Julie a rendu le vidéoprojecteur et a toujours la caméra, qu'elle devait rendre le 30 octobre : elle est en retard. Seuls PC-0101 et VP-0001 peuvent être prêtés.
Se connecter avec PDO
Une connexion PDO se crée avec trois informations : le DSN (Data Source Name), qui dit quel pilote utiliser et où est la base, le nom du compte et son mot de passe. Un quatrième argument, facultatif, règle le comportement de la connexion.
<?php
declare(strict_types=1);
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=pretmat;charset=utf8mb4';
$pdo = new PDO($dsn, 'pretmat_app', 'mot-de-passe-du-compte', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
| Élément | Rôle |
|---|---|
mysql: | Le pilote : pdo_mysql, qui sert aussi pour MariaDB |
host=127.0.0.1 | Le serveur. localhost passe par une socket locale, 127.0.0.1 par TCP ; avec Docker, le nom du service (db) |
dbname=pretmat | La base de données |
charset=utf8mb4 | L'encodage des échanges : indispensable, sinon les accents peuvent être mal transmis. utf8mb4 est le vrai UTF-8 de MySQL (utf8 seul ne gère pas tous les caractères) |
ATTR_ERRMODE => ERRMODE_EXCEPTION | Une erreur SQL lève une exception PDOException. C'est le défaut depuis PHP 8.0 ; l'écrire rend le code explicite |
ATTR_DEFAULT_FETCH_MODE => FETCH_ASSOC | Les lignes arrivent en tableaux associatifs ($ligne['reference']), sans doublon numérique |
ATTR_EMULATE_PREPARES => false | Les requêtes préparées sont de vraies requêtes préparées par le serveur MySQL : LIMIT :n accepte ses valeurs et un marqueur ne peut pas servir deux fois sans erreur visible |
Une fonction de connexion réutilisable
Chaque page qui lit la base a besoin de la connexion. Plutôt que de recopier ces lignes, range-les dans une fonction qui crée la connexion une seule fois par requête et la renvoie aux appels suivants :
<?php
declare(strict_types=1);
// src/bdd.php
function bdd(): PDO
{
static $pdo = null; // conservée d'un appel à l'autre, pendant la requête
if ($pdo === null) {
$dsn = sprintf(
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
getenv('DB_HOST') ?: '127.0.0.1',
getenv('DB_PORT') ?: '3306',
getenv('DB_NAME') ?: 'pretmat',
);
$pdo = new PDO($dsn, getenv('DB_USER') ?: '', getenv('DB_PASSWORD') ?: '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return $pdo;
}
getenv() lit les variables d'environnement, fixées par le compose.yaml de Docker, la configuration d'Apache ou un fichier .env (chapitre 15). Une page n'a plus qu'à faire require __DIR__ . '/../src/bdd.php'; puis $pdo = bdd();. Le chapitre 11 range ensuite cette connexion dans une classe.
Les erreurs de connexion fréquentes
| Message | Cause probable | Correction |
|---|---|---|
could not find driver | L'extension pdo_mysql n'est pas chargée | Active-la dans php.ini (extension=pdo_mysql) ou installe-la (docker-php-ext-install pdo_mysql) |
SQLSTATE[HY000] [2002] Connection refused | Le serveur MySQL est arrêté, ou n'écoute pas sur ce port | Démarre-le, vérifie le port (3306 par défaut) |
SQLSTATE[HY000] [2002] No such file or directory | host=localhost cherche une socket locale qui n'existe pas | Utilise 127.0.0.1, ou le nom du service Docker |
SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for db failed | Le nom d'hôte n'existe pas sur cette machine (le nom du service Docker utilisé hors de Docker, par exemple) | Corrige l'hôte |
SQLSTATE[HY000] [1045] Access denied for user 'pretmat_app'@'172.18.0.3' | Mauvais mot de passe, ou compte non autorisé depuis cette adresse | Vérifie le compte et son hôte ('pretmat_app'@'%') |
SQLSTATE[HY000] [1049] Unknown database 'pretmat' | La base n'existe pas, ou faute de frappe | Exécute le script de création |
query() : une requête sans paramètre
Quand la requête ne contient aucune donnée variable, query() l'exécute directement et renvoie un objet PDOStatement que l'on parcourt :
<?php
$pdo = bdd();
$nbMateriels = (int) $pdo->query('SELECT COUNT(*) FROM materiel')->fetchColumn();
foreach ($pdo->query('SELECT reference, libelle FROM materiel ORDER BY reference') as $m) {
echo $m['reference'] . ' : ' . $m['libelle'] . "\n";
}
Dès qu'une valeur vient de l'extérieur (formulaire, URL, cookie, fichier importé, et même la base), on passe par une requête préparée.
Les requêtes préparées : prepare() et execute()
Une requête préparée se fait en deux temps :
prepare()envoie au serveur le texte de la requête, avec des marqueurs (:categorieou?) à la place des valeurs. Le serveur l'analyse et la compile : la structure de la requête est figée.execute()envoie les valeurs. Le serveur les place dans les emplacements prévus, comme des données, sans jamais les analyser comme du SQL.
<?php
$categorie = 'portable'; // une valeur venue d'un formulaire, déjà validée
$stmt = $pdo->prepare(
'SELECT id, reference, libelle FROM materiel
WHERE categorie = :categorie AND etat = :etat
ORDER BY reference'
);
$stmt->execute(['categorie' => $categorie, 'etat' => 'disponible']);
$portables = $stmt->fetchAll();
Une même requête préparée peut être exécutée plusieurs fois avec des valeurs différentes : prépare-la avant une boucle, pas à l'intérieur.
<?php
$stmt = $pdo->prepare('UPDATE materiel SET etat = :etat WHERE reference = :reference');
foreach (['TAB-0003' => 'disponible', 'PC-0087' => 'reforme'] as $reference => $etat) {
$stmt->execute(['etat' => $etat, 'reference' => $reference]);
}
Marqueurs nommés ou positionnels
| Marqueurs nommés | Marqueurs positionnels | |
|---|---|---|
| Syntaxe | WHERE categorie = :categorie | WHERE categorie = ? |
| Valeurs | Tableau associatif : ['categorie' => $c] | Liste dans l'ordre : [$c] |
| Lisibilité | Meilleure dès trois valeurs | Pratique pour une ou deux valeurs |
Ne mélange pas les deux dans une même requête. Avec des marqueurs nommés, le tableau doit contenir exactement les marqueurs de la requête, ni plus, ni moins : une clé en trop ou en moins provoque SQLSTATE[HY093]: Invalid parameter number. Les deux-points devant la clé ([':categorie' => $c]) sont facultatifs.
bindValue() et les types
execute([...]) transmet toutes les valeurs comme des chaînes (et null comme NULL). C'est sans conséquence dans presque tous les cas : MySQL convertit '4' en 4 pour comparer avec une colonne INT. Une exception : LIMIT et OFFSET. Avec l'émulation des requêtes préparées (le réglage par défaut de PDO), la valeur est insérée entre apostrophes, et LIMIT '10' est une erreur de syntaxe. Lie ces valeurs avec leur type :
<?php
$parPage = 10;
$page = 2;
$stmt = $pdo->prepare('SELECT reference, libelle FROM materiel ORDER BY reference LIMIT :limite OFFSET :decalage');
$stmt->bindValue('limite', $parPage, PDO::PARAM_INT);
$stmt->bindValue('decalage', ($page - 1) * $parPage, PDO::PARAM_INT);
$stmt->execute();
| Constante | Type SQL transmis |
|---|---|
PDO::PARAM_STR | Chaîne (le défaut) |
PDO::PARAM_INT | Entier |
PDO::PARAM_BOOL | Booléen |
PDO::PARAM_NULL | NULL |
Tu croiseras aussi bindParam(), qui lie une variable (par référence) au lieu de sa valeur : la valeur est lue au moment de execute(). C'est rarement utile ; bindValue() ou le tableau d'execute() sont plus clairs.
Ce qu'un marqueur ne peut pas remplacer
Un marqueur remplace une valeur, jamais un morceau de la structure de la requête. Quatre cas reviennent souvent.
Un nom de colonne ou un sens de tri. ORDER BY :colonne ne trie pas : la colonne serait comparée à une chaîne constante. Pour un tri choisi par l'utilisateur, on passe par une liste blanche qui associe chaque choix permis à un nom de colonne écrit dans le code :
<?php
$colonnesTri = ['reference' => 'reference', 'valeur' => 'valeur', 'achat' => 'date_achat'];
$colonne = $colonnesTri[$_GET['tri'] ?? ''] ?? 'reference'; // jamais la saisie elle-même
$sens = ($_GET['sens'] ?? '') === 'desc' ? 'DESC' : 'ASC';
$materiels = $pdo->query("SELECT reference, libelle, valeur FROM materiel ORDER BY $colonne $sens")->fetchAll();
La chaîne SQL contient des variables, mais leurs valeurs ne peuvent être que des textes écrits dans le code : l'utilisateur choisit parmi elles, il ne fournit rien.
Une liste pour IN. Un marqueur vaut une valeur. Pour une liste, on génère autant de marqueurs que de valeurs :
<?php
$ids = [2, 5]; // par exemple, la sélection en session
if ($ids === []) {
$materiels = [];
} else {
$marqueurs = implode(', ', array_fill(0, count($ids), '?')); // '?, ?'
$stmt = $pdo->prepare("SELECT id, reference FROM materiel WHERE id IN ($marqueurs)");
$stmt->execute($ids);
$materiels = $stmt->fetchAll();
}
Le test de la liste vide évite la requête IN (), qui est une erreur de syntaxe.
Un motif LIKE. Les % font partie de la valeur, pas de la requête : WHERE libelle LIKE :motif avec ['motif' => '%' . $recherche . '%']. Si la saisie contient elle-même % ou _, ils agissent comme des jokers : échappe-les avec addcslashes($recherche, '%_\\') si tu veux les chercher littéralement.
Le même marqueur deux fois. WHERE libelle LIKE :q OR reference LIKE :q fonctionne avec l'émulation, mais échoue avec de vraies requêtes préparées (Invalid parameter number). Utilise deux marqueurs, :q1 et :q2, avec la même valeur.
L'injection SQL : comprendre pour l'empêcher
Voici le code de connexion d'une version de Prêtmat écrite trop vite :
<?php
// connexion.php : VULNÉRABLE
$email = $_POST['email'];
$sql = "SELECT id, nom, mot_de_passe, role FROM utilisateur WHERE email = '$email'";
$utilisateur = $pdo->query($sql)->fetch();
La saisie est collée dans le texte de la requête. Tant que l'utilisateur tape une adresse, tout va bien. Mais si la saisie contient une apostrophe, celle-ci ferme la chaîne SQL, et tout ce qui suit est lu par le serveur comme du code SQL. Un attaquant peut ainsi modifier la condition WHERE pour qu'elle soit toujours vraie, ou ajouter un UNION SELECT qui renvoie le contenu d'autres tables. C'est l'injection SQL, l'une des failles les plus graves et les plus fréquentes du Top 10 de l'OWASP.
La correction tient en deux lignes :
<?php
// connexion.php : corrigé
$stmt = $pdo->prepare('SELECT id, nom, mot_de_passe, role FROM utilisateur WHERE email = :email');
$stmt->execute(['email' => $_POST['email'] ?? '']);
$utilisateur = $stmt->fetch();
Avec la requête préparée, la saisie est envoyée après l'analyse de la requête : quel que soit son contenu, apostrophes comprises, elle reste une valeur à comparer avec la colonne email. Au pire, aucune ligne ne correspond.
Les requêtes préparées empêchent l'injection ; trois mesures limitent les dégâts si une faille subsiste ailleurs : un compte MySQL aux droits minimaux, des messages d'erreur qui ne révèlent pas les noms des tables, et des mots de passe hachés. Le cours SQL explique l'injection côté base (injection SQL et requêtes préparées), et le cours Sécurité des applications la replace parmi les autres injections (NoSQL, commandes système).
INSERT, UPDATE et DELETE
Les requêtes qui modifient les données s'écrivent de la même façon. Deux méthodes donnent le résultat :
$stmt->rowCount(): le nombre de lignes modifiées ou supprimées ;$pdo->lastInsertId(): l'identifiant auto-incrémenté de la ligne qu'on vient d'insérer (sous forme de chaîne).
<?php
// Ajouter un matériel : $valeurs vient de validerMateriel() (chapitre 4)
$valeurs = ['reference' => 'CAM-0002', 'libelle' => 'Caméra Logitech', 'categorie' => 'camera',
'date_achat' => '2026-10-15', 'valeur' => 329.0];
$stmt = $pdo->prepare(
'INSERT INTO materiel (reference, libelle, categorie, date_achat, valeur)
VALUES (:reference, :libelle, :categorie, :date_achat, :valeur)'
);
$stmt->execute($valeurs);
$idMateriel = (int) $pdo->lastInsertId();
// Passer un matériel en réparation
$stmt = $pdo->prepare("UPDATE materiel SET etat = 'en_reparation' WHERE id = :id");
$stmt->execute(['id' => $idMateriel]);
if ($stmt->rowCount() === 0) {
// aucun matériel avec cet id, ou il était déjà en réparation
}
Attention, avec MySQL, rowCount() après un UPDATE compte les lignes réellement modifiées : si la nouvelle valeur est égale à l'ancienne, la ligne n'est pas comptée. Pour savoir si une ligne existe, fais un SELECT.
Le 'en_reparation' écrit dans la requête n'est pas une faille : c'est une constante choisie par le développeur, pas une saisie.
Exercices
Exercice 1 sur 4 Trouver l'injection, comme à l'E7 Réussi À revoir
Voici un extrait de la page de recherche de Prêtmat. Les numéros de ligne sont à gauche.
1 <?php
2 $categorie = $_GET['categorie'] ?? 'portable';
3 $recherche = trim($_GET['q'] ?? '');
4 $sql = "SELECT id, reference, libelle FROM materiel WHERE categorie = '"
5 . $categorie . "' AND libelle LIKE '%" . $recherche . "%'";
6 $stmt = $pdo->prepare($sql);
7 $stmt->execute();
8 $resultats = $stmt->fetchAll();
- Nomme la faille, indique les lignes concernées et explique le mécanisme.
- L'auteur affirme que la ligne 6 le protège. Que lui réponds-tu ?
- Réécris les lignes 4 à 7.
Voir la réponseMasquer la réponse de l'exercice 1
- Injection SQL, lignes 4 et 5 :
$categorieet$recherche, qui viennent de l'URL (lignes 2 et 3), sont concaténées dans le texte de la requête. Une valeur contenant une apostrophe ferme la chaîne SQL ; la suite est interprétée comme du SQL, ce qui permet de modifier la condition ou d'ajouter unUNION SELECTpour lire d'autres tables (la tableutilisateuret ses empreintes de mots de passe, par exemple). prepare()ne protège que les valeurs passées par des marqueurs. Ici, la requête est déjà construite avec les saisies quand elle est préparée, et aucun marqueur n'est lié : la ligne 6 n'apporte aucune protection.- Correction :
4 $sql = 'SELECT id, reference, libelle FROM materiel
5 WHERE categorie = :categorie AND libelle LIKE :motif';
6 $stmt = $pdo->prepare($sql);
7 $stmt->execute(['categorie' => $categorie, 'motif' => '%' . $recherche . '%']);
On peut ajouter une validation de $categorie par liste blanche (in_array($categorie, CATEGORIES, true)), qui renvoie une erreur 400 si la valeur n'est pas prévue : c'est une défense supplémentaire, pas un remplacement de la requête préparée.
Tu avais trouvé ?
Exercice 2 sur 4 Les emprunts d'un employé Réussi À revoir
Écris le code qui affiche, pour l'utilisateur dont l'identifiant est dans $_GET['id'], la référence du matériel et la date de retour prévue de ses emprunts en cours (date_retour à NULL), du retour le plus proche au plus lointain. Valide l'identifiant. Avec le jeu de données, qu'obtiens-tu pour l'id 2 ?
Voir la réponseMasquer la réponse de l'exercice 2
<?php
declare(strict_types=1);
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if ($id === null || $id === false) {
http_response_code(400);
exit('Identifiant invalide');
}
$stmt = bdd()->prepare(
'SELECT m.reference, e.date_retour_prevue
FROM emprunt e
JOIN materiel m ON m.id = e.id_materiel
WHERE e.id_utilisateur = :id AND e.date_retour IS NULL
ORDER BY e.date_retour_prevue'
);
$stmt->execute(['id' => $id]);
foreach ($stmt as $e) {
echo e($e['reference']) . ' : à rendre le ' . e($e['date_retour_prevue']) . '<br>';
}
Pour l'id 2 (Julie Lefèvre), une seule ligne : CAM-0001 : à rendre le 2026-10-30. Son emprunt du vidéoprojecteur a une date_retour, il n'est plus en cours. La condition s'écrit IS NULL : = NULL ne renvoie jamais rien (NULL et la logique à trois valeurs). Le chapitre 8 montre comment afficher la date au format 30/10/2026.
Tu avais trouvé ?
Exercice 3 sur 4 Un tri choisi par l'utilisateur Réussi À revoir
La liste du matériel propose un tri par référence, libellé ou valeur, croissant ou décroissant, transmis dans l'URL : materiels.php?tri=valeur&sens=desc. Un collègue propose :
<?php
$stmt = $pdo->prepare('SELECT reference, libelle, valeur FROM materiel ORDER BY :tri :sens');
$stmt->execute(['tri' => $_GET['tri'], 'sens' => $_GET['sens']]);
Pourquoi cela ne fonctionne-t-il pas ? Écris une version correcte et sûre.
Voir la réponseMasquer la réponse de l'exercice 3
Un marqueur remplace une valeur, pas un nom de colonne ni un mot-clé. ORDER BY :tri trie selon une chaîne constante (toutes les lignes ont la même) : le tri est sans effet. Et :sens à la place de ASC ou DESC est une erreur de syntaxe.
<?php
$colonnes = ['reference' => 'reference', 'libelle' => 'libelle', 'valeur' => 'valeur'];
$colonne = $colonnes[$_GET['tri'] ?? ''] ?? 'reference';
$sens = ($_GET['sens'] ?? '') === 'desc' ? 'DESC' : 'ASC';
$materiels = $pdo->query("SELECT reference, libelle, valeur FROM materiel ORDER BY $colonne $sens, id")->fetchAll();
Les noms insérés dans la requête viennent du tableau $colonnes et des deux chaînes 'DESC' et 'ASC', écrits dans le code : la saisie sert seulement à choisir. Le id en second critère rend l'ordre stable entre deux matériels de même valeur.
Tu avais trouvé ?
Exercice 4 sur 4 Corriger une pagination Réussi À revoir
Ce code lève SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax ... near ''10' OFFSET '20''. Explique et corrige.
<?php
$stmt = $pdo->prepare('SELECT reference FROM materiel ORDER BY reference LIMIT :n OFFSET :d');
$stmt->execute(['n' => 10, 'd' => 20]);
Voir la réponseMasquer la réponse de l'exercice 4
execute([...]) transmet les valeurs comme des chaînes. La connexion utilise l'émulation des requêtes préparées (réglage par défaut de PDO) : PDO insère les valeurs entre apostrophes, et MySQL refuse LIMIT '10'. Deux corrections, à combiner :
<?php
$stmt = $pdo->prepare('SELECT reference FROM materiel ORDER BY reference LIMIT :n OFFSET :d');
$stmt->bindValue('n', 10, PDO::PARAM_INT);
$stmt->bindValue('d', 20, PDO::PARAM_INT);
$stmt->execute();
et l'option PDO::ATTR_EMULATE_PREPARES => false à la connexion, qui fait préparer les requêtes par le serveur. Les valeurs de LIMIT et OFFSET viennent souvent de l'URL (?page=3) : elles se valident en entiers positifs avant le calcul.
Tu avais trouvé ?