Exercice : interroger une base de données depuis une page web
📚 Présentation
Exercice « façon tableur » : une page PHP interroge une base MariaDB et affiche le résultat dans un tableau triable en cliquant sur les en-têtes de colonnes, comme dans un tableur (Excel, LibreOffice Calc…).
Objectifs pédagogiques
- Comprendre le cycle formulaire → requête SQL → affichage
- Savoir écrire une requête
SELECTavec des conditions (WHERE) - Comprendre pourquoi on utilise des requêtes préparées plutôt que d'insérer directement les valeurs saisies dans la requête SQL (injection SQL)
- Trier les résultats en cliquant sur un en-tête de colonne
🗂️ Fichiers à créer
| Fichier | Rôle |
|---|---|
setup.sql |
Crée la base bibliotheque_exercice et une table livres avec 15 livres d'exemple |
db.php |
Connexion PDO à la base MariaDB |
index.php |
Formulaire de recherche + affichage des résultats en tableau |
style.css |
Mise en forme « façon tableur » (lignes alternées, tri cliquable) |
⚙️ Prérequis
Environnement nécessaire
- Apache 2.4+ avec
mod_phpou PHP-FPM - PHP 7.4+ avec l'extension
pdo_mysqlactivée - MariaDB 10.3+ (ou MySQL 5.7+)
🗂️ Installation pas à pas
1. Créer la base et les données
(ou coller le contenu de setup.sql dans phpMyAdmin, onglet SQL)
2. Adapter les identifiants dans db.php
Ajuster $utilisateur et $motDePasse selon votre configuration MariaDB.
Bonne pratique en classe
Plutôt que d'utiliser root, créer un utilisateur MariaDB dédié avec des droits limités à cette seule base :
3. Déployer sur Apache
Placer le dossier dans le répertoire servi par Apache, par exemple :
Puis ouvrir http://localhost/exercice-web-bdd/ dans un navigateur.
🧩 Fonctionnalités
| Fonctionnalité | Mécanisme |
|---|---|
| Recherche par titre ou auteur | LIKE sur deux colonnes, jointes par OR |
| Filtre par genre | Liste déroulante remplie via SELECT DISTINCT genre |
| Filtre « disponibles uniquement » | Case à cocher → condition disponible = 1 |
| Tri façon tableur | Clic sur un en-tête de colonne, conserve les filtres en cours |
🔐 Sécurité : le point clé pédagogique
Ne jamais faire ceci
Que se passe-t-il si un élève tape%' OR '1'='1 dans le champ de recherche ? C'est le déclic classique pour comprendre l'injection SQL et pourquoi on utilise des marqueurs (:recherche) exécutés séparément de la requête.
Un piège plus subtil : le tri
Les requêtes préparées protègent les valeurs, pas les noms de colonnes ou de tables. Le nom de colonne de tri (?tri=...) ne peut donc pas passer par un simple :parametre. La solution retenue ici est une liste blanche ($colonnesAutorisees) : toute valeur absente de cette liste est ignorée au profit d'une valeur par défaut.
- Anti-injection SQL sur les valeurs : requêtes préparées PDO avec placeholders nommés
- Anti-injection SQL sur les noms de colonnes : liste blanche de valeurs autorisées
- Anti-XSS : toutes les sorties HTML passent par
htmlspecialchars() - Identifiants de connexion en clair dans
db.php: acceptable en local pour l'exercice, jamais en production — bon sujet de discussion en classe (pourquoi ne verse-t-on jamais un mot de passe de base de données sur GitHub ?)
🎓 Déroulé pédagogique suggéré
- Lecture guidée : commencer par
db.php, le plus court — il introduit PDO et la notion de connexion. - Suivre le trajet d'une requête : partir du formulaire HTML dans
index.php, montrer comment$_GETrécupère les valeurs saisies, puis comment elles se retrouvent (ou non !) dans la requête SQL. - Le point clé : les requêtes préparées (voir encadré ci-dessus).
- Pistes d'extension, par ordre de difficulté :
- Ajouter un filtre par année (avant/après une date donnée)
- Ajouter une colonne « nombre de pages » et l'afficher
- Limiter l'affichage à 10 résultats par page (pagination)
- Ajouter un bouton pour exporter les résultats affichés en CSV
- (Plus avancé) Ajouter une table
auteursséparée et faire une jointure — première approche du modèle relationnel
🗄️ Structure de la table livres
CREATE TABLE livres (
id INT AUTO_INCREMENT PRIMARY KEY,
titre VARCHAR(150) NOT NULL,
auteur VARCHAR(100) NOT NULL,
annee SMALLINT NOT NULL,
genre VARCHAR(50) NOT NULL,
disponible TINYINT(1) NOT NULL DEFAULT 1 -- 1 = emprunt possible, 0 = déjà emprunté
);
💻 Code source complet
Copiez chacun des blocs ci-dessous dans un fichier du même nom, dans le même dossier.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |