C'est quoi une base de données SQL ?
Une base de données, c'est un classeur organisé. Chaque table est un onglet, chaque ligne est un enregistrement, chaque colonne est un champ :
Table : utilisateurs
+----+---------+---------------------+----------+
| id | nom | email | role |
+----+---------+---------------------+----------+
| 1 | Alice | alice@exemple.fr | admin |
| 2 | Bob | bob@exemple.fr | user |
| 3 | Charlie | charlie@exemple.fr | user |
+----+---------+---------------------+----------+
SQL (Structured Query Language) est le langage pour parler à la base de données : lire, ajouter, modifier, supprimer des données.
Cette leçon montre comment brancher PHP à une base. Pour maîtriser le SQL lui-même (SELECT, JOIN, agrégats, sécurité), suis le cours SQL dédié, avec des requêtes exécutables dans le navigateur.
PDO : se connecter à la base
PHP utilise PDO (PHP Data Objects) pour se connecter aux bases de données. PDO fonctionne avec MySQL, PostgreSQL, SQLite... :
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=mon_site;charset=utf8mb4',
'utilisateur', // nom d'utilisateur BDD
'mot_de_passe', // mot de passe BDD
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
} catch (PDOException $e) {
die("Erreur de connexion : " . $e->getMessage());
}
Les deux options PDO sont importantes :
ERRMODE_EXCEPTION: lance une exception en cas d'erreur (au lieu de silence)FETCH_ASSOC: renvoie les résultats en tableaux associatifs
CRUD : les 4 opérations
Tout ce que tu feras avec une base de données tient en 4 opérations :
// CREATE : insérer des données
$sql = "INSERT INTO utilisateurs (nom, email, role) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['Alice', 'alice@exemple.fr', 'admin']);
// READ : lire des données
$sql = "SELECT * FROM utilisateurs WHERE role = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(['admin']);
$admins = $stmt->fetchAll();
foreach ($admins as $admin) {
echo $admin['nom'] . " : " . $admin['email'] . "<br>";
}
// UPDATE : modifier des données
$sql = "UPDATE utilisateurs SET role = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(['moderator', 2]);
// DELETE : supprimer des données
$sql = "DELETE FROM utilisateurs WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([3]);
Requêtes préparées : la règle d'or
JAMAIS concaténer des variables dans une requête SQL. C'est la porte ouverte à l'injection SQL :
Un code construit sa requête en collant la saisie : $pdo->query("SELECT * FROM users WHERE nom = '" . $_GET['nom'] . "'"). L'utilisateur passe nom = ' OR '1'='1. Avant de dérouler : que devient la requête réellement exécutée, et pourquoi une requête préparée (prepare + execute([$nom])) empêche-t-elle ce problème ?
Voir la réponse
La requête devient SELECT * FROM users WHERE nom = '' OR '1'='1'. La condition est toujours vraie : elle renvoie tous les utilisateurs (injection SQL, contournement d'authentification, fuite de données). Cause : la saisie a été collée dans le texte même de la requête, donc interprétée comme du code SQL. Une requête préparée sépare la structure et les données : $stmt = $pdo->prepare("SELECT * FROM users WHERE nom = ?"); $stmt->execute([$_GET['nom']]);. Le ? est un emplacement ; la valeur est envoyée séparément au moteur, traitée toujours comme une simple donnée, jamais comme du SQL. Quoi que tape l'utilisateur, c'est cherché littéralement comme un nom : aucune injection possible.
// ⛔ DANGER : injection SQL possible !
$sql = "SELECT * FROM utilisateurs WHERE email = '$email'";
// Un attaquant peut envoyer : ' OR 1=1 --
// → SELECT * FROM utilisateurs WHERE email = '' OR 1=1 --'
// → Renvoie TOUS les utilisateurs !
// ✅ CORRECT : requête préparée
$sql = "SELECT * FROM utilisateurs WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
$user = $stmt->fetch();
Les ? sont des placeholders. PDO s'occupe d'échapper les valeurs : impossible d'injecter du SQL malveillant.
Exemple complet : liste d'articles
<?php
// connexion.php (à inclure partout)
$pdo = new PDO('mysql:host=localhost;dbname=blog;charset=utf8mb4',
'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// Lire tous les articles publiés
$stmt = $pdo->prepare("SELECT * FROM articles WHERE publie = ? ORDER BY date_creation DESC");
$stmt->execute([1]);
$articles = $stmt->fetchAll();
?>
<h1>Blog</h1>
<?php if (empty($articles)): ?>
<p>Aucun article pour le moment.</p>
<?php else: ?>
<?php foreach ($articles as $article): ?>
<article>
<h2><?= htmlspecialchars($article['titre']) ?></h2>
<p><?= htmlspecialchars($article['extrait']) ?></p>
<small><?= $article['date_creation'] ?></small>
</article>
<?php endforeach; ?>
<?php endif; ?>
Prévention de l'injection SQL
Résumé des règles de sécurité pour la base de données :
- Toujours utiliser des requêtes préparées avec
? - Jamais concaténer
$_GET,$_POSTou$_COOKIEdans une requête - Limiter les droits de l'utilisateur BDD (pas de DROP, pas de GRANT)
- Valider les données avant la requête (type, longueur, format)
À vous d'essayer
Le bac à sable n'a pas de base de données : on simule une table avec un tableau PHP et on filtre comme un SELECT ... WHERE. Modifiez, puis exécutez :
<?php
// Simulation d'une table "utilisateurs"
$utilisateurs = [
['id' => 1, 'nom' => 'Alice', 'role' => 'admin'],
['id' => 2, 'nom' => 'Bob', 'role' => 'user'],
['id' => 3, 'nom' => 'Charlie', 'role' => 'user'],
];
// Equivalent de : SELECT * FROM utilisateurs WHERE role = 'user'
$admins = array_filter($utilisateurs, fn($u) => $u['role'] === 'user');
foreach ($admins as $u) {
echo $u['id'] . " - " . $u['nom'] . "\n";
}
?>
What is a SQL database?
A database is an organized filing system. Each table is a tab, each row is a record, each column is a field:
Table: users
+----+---------+---------------------+----------+
| id | name | email | role |
+----+---------+---------------------+----------+
| 1 | Alice | alice@example.com | admin |
| 2 | Bob | bob@example.com | user |
| 3 | Charlie | charlie@example.com | user |
+----+---------+---------------------+----------+
SQL (Structured Query Language) is the language to talk to the database: read, add, update, delete data.
This lesson shows how to connect PHP to a database. To master SQL itself (SELECT, JOIN, aggregates, security), follow the dedicated SQL course — with queries you can run right in the browser.
PDO: connecting to the database
PHP uses PDO (PHP Data Objects) to connect to databases. PDO works with MySQL, PostgreSQL, SQLite...:
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=my_site;charset=utf8mb4',
'username', // DB username
'password', // DB password
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
} catch (PDOException $e) {
die("Connection error: " . $e->getMessage());
}
The two PDO options are important:
ERRMODE_EXCEPTION— throws an exception on error (instead of silence)FETCH_ASSOC— returns results as associative arrays
CRUD: the 4 operations
Everything you do with a database fits into 4 operations:
// CREATE — insert data
$sql = "INSERT INTO users (name, email, role) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['Alice', 'alice@example.com', 'admin']);
// READ — read data
$sql = "SELECT * FROM users WHERE role = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(['admin']);
$admins = $stmt->fetchAll();
foreach ($admins as $admin) {
echo $admin['name'] . " — " . $admin['email'] . "<br>";
}
// UPDATE — modify data
$sql = "UPDATE users SET role = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(['moderator', 2]);
// DELETE — remove data
$sql = "DELETE FROM users WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([3]);
Prepared statements: the golden rule
NEVER concatenate variables into a SQL query. It's an open door to SQL injection:
A piece of code builds its query by concatenating user input: $pdo->query("SELECT * FROM users WHERE nom = '" . $_GET['nom'] . "'"). The user passes nom = ' OR '1'='1. Before scrolling down: what does the actually executed query become, and why does a prepared statement (prepare + execute([$nom])) prevent this problem?
See the answer
The query becomes SELECT * FROM users WHERE nom = '' OR '1'='1'. The condition is always true: it returns all users (SQL injection, authentication bypass, data leak). Root cause: the input was pasted straight into the query text and therefore interpreted as SQL code. A prepared statement separates structure from data: $stmt = $pdo->prepare("SELECT * FROM users WHERE nom = ?"); $stmt->execute([$_GET['nom']]);. The ? is a placeholder; the value is sent separately to the engine and always treated as plain data, never as SQL. Whatever the user types is searched literally as a name: no injection is possible.
// ⛔ DANGER — SQL injection possible!
$sql = "SELECT * FROM users WHERE email = '$email'";
// An attacker can send: ' OR 1=1 --
// → SELECT * FROM users WHERE email = '' OR 1=1 --'
// → Returns ALL users!
// ✅ CORRECT — prepared statement
$sql = "SELECT * FROM users WHERE email = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$email]);
$user = $stmt->fetch();
The ? are placeholders. PDO handles escaping values — impossible to inject malicious SQL.
Complete example: article list
<?php
// connection.php (include everywhere)
$pdo = new PDO('mysql:host=localhost;dbname=blog;charset=utf8mb4',
'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// Read all published articles
$stmt = $pdo->prepare("SELECT * FROM articles WHERE published = ? ORDER BY created_at DESC");
$stmt->execute([1]);
$articles = $stmt->fetchAll();
?>
<h1>Blog</h1>
<?php if (empty($articles)): ?>
<p>No articles yet.</p>
<?php else: ?>
<?php foreach ($articles as $article): ?>
<article>
<h2><?= htmlspecialchars($article['title']) ?></h2>
<p><?= htmlspecialchars($article['excerpt']) ?></p>
<small><?= $article['created_at'] ?></small>
</article>
<?php endforeach; ?>
<?php endif; ?>
SQL injection prevention
Summary of database security rules:
- Always use prepared statements with
? - Never concatenate
$_GET,$_POSTor$_COOKIEin a query - Limit permissions of the DB user (no DROP, no GRANT)
- Validate data before querying (type, length, format)
Exemple complet : le CRUD avec PDO
Le cycle complet en PDO avec des requêtes préparées : créer la table, insérer (CREATE/INSERT), lire (SELECT), compter (COUNT). À recopier sur un serveur PHP qui a PDO : l'éditeur en ligne de cette page n'a pas l'extension PDO, donc ce bloc-ci est donné en référence (non exécutable ici).
<?php
// PDO + SQLite en mémoire : une vraie base de données, jetable, sans serveur.
// La même API PDO fonctionne avec MySQL (il suffit de changer le DSN).
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// CREATE : on crée la table
$pdo->exec("CREATE TABLE utilisateurs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
email TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user'
)");
// INSERT avec requête préparée (protège contre l'injection SQL)
$insert = $pdo->prepare(
"INSERT INTO utilisateurs (nom, email, role) VALUES (:nom, :email, :role)"
);
$insert->execute(['nom' => 'Alice', 'email' => 'alice@exemple.fr', 'role' => 'admin']);
$insert->execute(['nom' => 'Bob', 'email' => 'bob@exemple.fr', 'role' => 'user']);
$insert->execute(['nom' => 'Charlie', 'email' => 'charlie@exemple.fr', 'role' => 'user']);
// SELECT : on lit toutes les lignes
$stmt = $pdo->query("SELECT * FROM utilisateurs ORDER BY id");
foreach ($stmt as $u) {
echo sprintf("#%d %-8s %-22s %s\n", $u['id'], $u['nom'], $u['email'], $u['role']);
}
// COUNT
$total = $pdo->query("SELECT COUNT(*) FROM utilisateurs")->fetchColumn();
echo "\nTotal : $total utilisateurs\n";
?>
🎯 Pratique
S'entraîner (clique pour ouvrir) :
✨ Prompt IA
Copiez ce prompt dans Claude ou ChatGPT :
Crée une classe PHP « Database » qui encapsule PDO avec des méthodes : query($sql, $params), find($table, $id), insert($table, $data), update($table, $id, $data), delete($table, $id). Toujours avec des requêtes préparées.
💬 Ré-explique sans regarder
Sans relire le code de l'IA : avec tes mots, pourquoi la classe « Database » doit-elle toujours passer par prepare() + execute([$params]) au lieu de concaténer les valeurs dans le SQL ?
prepare(), le SQL et les données voyagent séparément. PDO envoie d'abord le gabarit de requête avec ses ? (ou :nom), puis les valeurs à part : elles sont traitées comme des données pures, jamais comme du code SQL. Donc même si un utilisateur tape ' OR 1=1 --, ce texte reste une simple valeur de recherche, il ne devient jamais une instruction. La concaténation, elle, fabrique la requête finale en mélangeant code et données : l'attaquant peut alors injecter du SQL. Bonus : centraliser ça dans une classe garantit qu'aucune requête du projet n'oublie la règle.⚖️ Juge le code de l'IA
L'IA te propose ce code pour chercher un utilisateur par email. Ton rôle de relecteur : l'accepter tel quel ou le rejeter, et dire pourquoi.
$email = $_GET['email'];
$sql = "SELECT * FROM utilisateurs WHERE email = '$email'";
$user = $pdo->query($sql)->fetch();
$_GET['email'] est concaténé directement dans le SQL : c'est une injection SQL manuelle. Un attaquant qui passe ?email=' OR 1=1 -- récupère toute la table ; pire ('; DROP TABLE utilisateurs; --) il peut détruire des données. Le correctif : $stmt = $pdo->prepare("SELECT * FROM utilisateurs WHERE email = ?"); $stmt->execute([$email]);. Règle non négociable : toute valeur venant de l'extérieur passe par un placeholder, jamais par concaténation.🧠 Rappel libre
Sans remonter dans la leçon : à quoi sert le ? dans WHERE email = ?, et en quoi protège-t-il contre l'injection SQL ?
? est un placeholder : un emplacement réservé dans le gabarit de requête. On passe la vraie valeur à part via $stmt->execute([$email]). PDO envoie d'abord le SQL, puis les valeurs séparément, et les traite comme de simples données, jamais comme du code. Résultat : un contenu malveillant comme ' OR 1=1 -- reste une valeur de recherche inerte et ne peut pas modifier la requête. C'est tout l'inverse de la concaténation, qui fond le SQL et la donnée en une seule chaîne.? dans WHERE id = ? ?Vous savez stocker et lire des données avec PDO. Dernière étape : dialoguer avec d'autres serveurs. On voit comment envoyer une requête HTTP depuis PHP pour appeler une API et récupérer ses données, comme une météo ou un paiement.
Leçon 8 : Comprendre la requête HTTP →