Leçon 7/8 10 min

Base de données SQL

Connectez PHP à une base de données avec PDO, maîtrisez le CRUD et protégez-vous contre l'injection SQL.

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 :

Prédisez avant de lire

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, $_POST ou $_COOKIE dans 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:

Predict before reading

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, $_POST or $_COOKIE in 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
Avec l'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
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 ?

Une bonne explication dit : avec 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
Accepter ou rejeter 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();
Rejeter sans hésiter. $_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
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 ?

Le ? 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.
Comment se connecter à une base MySQL en PHP ?
Pourquoi ne faut-il JAMAIS concaténer des variables dans une requête SQL ?
Quelle méthode PDO récupère toutes les lignes du résultat ?
Que représente le ? dans WHERE id = ? ?
Prochaine étape

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 →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement