Lesson 6/7 10 min

SQL databases

Connect PHP to a database with PDO, master CRUD operations and protect against SQL injection.

FR EN

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 vous ferez 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 :

// ⛔ 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:

// ⛔ 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";
?>
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

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.
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

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 = ? ?

Pour aller plus loin

Vous maîtrisez maintenant les fondamentaux de PHP. Voici les sujets avancés à explorer :

  • Laravel : le framework PHP le plus populaire Documentation
  • Symfony : framework d'entreprise, très utilisé en France Documentation
  • Composer : le gestionnaire de packages PHP Site officiel
  • PHP 8.x : named arguments, enums, fibers, readonly properties PHP.net
  • API REST : construire des API JSON avec PHP W3Schools

Références complètes : W3Schools PHP · PHP.net

La plomberie est installée

Félicitations. Votre bâtiment communique maintenant avec le monde extérieur : il reçoit des données, les stocke, les traite et répond. L'eau coule dans les tuyaux, les robinets fonctionnent, la chaudière chauffe.

Avec HTML (structure), CSS (façade), JavaScript (électricité) et PHP (plomberie), vous avez les 4 piliers du développement web.

La suite ? Python et Go pour élargir vos horizons au-delà du web : data science, automatisation, systèmes distribués. Les fondations sont solides. Continuez à construire.

Going further

You now master the fundamentals of PHP. Here are advanced topics to explore:

  • Laravel — the most popular PHP framework Documentation
  • Symfony — enterprise framework, widely used in Europe Documentation
  • Composer — the PHP package manager Official site
  • PHP 8.x — named arguments, enums, fibers, readonly properties PHP.net
  • REST API — build JSON APIs with PHP W3Schools

Full references: W3Schools PHP · PHP.net

The plumbing is installed

Congratulations. Your building now communicates with the outside world — it receives data, stores it, processes it and responds. Water flows through the pipes, faucets work, the boiler heats.

With HTML (structure), CSS (facade), JavaScript (electricity) and PHP (plumbing), you have the 4 pillars of web development.

What's next? Python and Go to broaden your horizons beyond the web — data science, automation, distributed systems. The foundations are solid. Keep building.

Commencer le cours Python →
Start the Python course →
Next step

You can store and read data with PDO. Final step: talking to other servers. We'll see how to send an HTTP request from PHP to call an API and fetch its data, like weather or a payment.

Lesson 7: Understanding the HTTP request →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement