Module 3Manipuler les données
Chapitre 7 sur 11
Écrire des scripts d'accès aux données
Se connecter à MySQL avec PDO, utiliser les requêtes préparées et réaliser un CRUD complet.
- ≈ 150 min
- 6 leçons
- 3 vidéos
- 3 exercices
Module 3Manipuler les données
Chapitre 7 sur 11
Se connecter à MySQL avec PDO, utiliser les requêtes préparées et réaliser un CRUD complet.
Objectifs
Avec ce chapitre, tu sauras…
query() et des requêtes préparées avec prepare() / execute() ;fetch, fetchAll, modes FETCH_ASSOC, FETCH_OBJ… ;Tu sais créer une base et écrire des requêtes SQL (M106). Il reste à les exécuter depuis PHP, pour que tes pages affichent et enregistrent des données réelles.
Leçon 1
| Extension | Bases | Style | Statut |
|---|---|---|---|
mysql_* | MySQL | procédural | supprimée depuis PHP 7 : ne plus jamais l'utiliser |
| MySQLi | MySQL / MariaDB seulement | procédural ou objet | maintenue |
| PDO (PHP Data Objects) | une douzaine de SGBD (MySQL, PostgreSQL, SQLite, SQL Server, Oracle…) | objet | maintenue, recommandée |
Leçon 2
| Élément | Rôle |
|---|---|
| DSN (Data Source Name) | décrit la source : mysql:host=…;port=…;dbname=…;charset=utf8mb4 |
| utilisateur, mot de passe | le compte MySQL (jamais root dans une application : M106, chapitre 8) |
ERRMODE_EXCEPTION | les erreurs SQL deviennent des exceptions : impossible de les ignorer sans le voir |
FETCH_ASSOC | mode de récupération par défaut |
charset=utf8mb4 | les accents et les caractères arabes sont transmis correctement |
Leçon 3
| Méthode | Usage | Renvoie |
|---|---|---|
$pdo->query($sql) | requête sans aucune donnée variable (lecture fixe) | un PDOStatement |
$pdo->exec($sql) | requête de modification sans donnée variable | le nombre de lignes touchées |
$pdo->prepare($sql) puis $stmt->execute([...]) | requête avec des valeurs variables (formulaire, URL) | true / false (et une exception en cas d'erreur) |
Leçon 4
| Méthode | Renvoie |
|---|---|
$stmt->fetch() | la ligne suivante (ou false s'il n'y en a plus) |
$stmt->fetchAll() | toutes les lignes, dans un tableau |
$stmt->fetchColumn() | la valeur de la première colonne de la ligne suivante (pratique pour un COUNT(*)) |
$stmt->rowCount() | le nombre de lignes modifiées par un INSERT, UPDATE ou DELETE |
Leçon 5
| Opération | SQL | HTTP (API REST, chapitre 10) | Page PHP |
|---|---|---|---|
| Create | INSERT | POST | ajouter.php |
| Read | SELECT | GET | liste.php, voir.php?id=3 |
Leçon 6
| Méthode | Rôle |
|---|---|
beginTransaction() | démarrer une transaction (désactive l'autocommit) |
commit() | valider toutes les opérations |
rollBack() | annuler toutes les opérations depuis beginTransaction() |
inTransaction() | savoir si une transaction est en cours |
1Les fonctions mysql_*…
2Le DSN PDO pour MySQL commence par…
3Pour qu'une erreur SQL lance une exception :
4Une valeur venant d'un formulaire s'utilise avec…
5Pour récupérer toutes les lignes :
6lastInsertId() renvoie…
7Une suppression doit se faire…
L'essentiel du chapitre
mysql_* supprimées.new PDO('mysql:host=…;dbname=…;charset=utf8mb4', $user, $mdp, [ERRMODE_EXCEPTION, FETCH_ASSOC]) dans un try/catch ; message neutre au visiteur.query() (sans variable), exec(), prepare() + execute([...]) (marqueurs ? ou :nom), bindValue ; lastInsertId(), rowCount().fetch(), fetchAll(), fetchColumn() ; modes FETCH_ASSOC, FETCH_OBJ, FETCH_CLASS, FETCH_KEY_PAIR.INSERT / SELECT / UPDATE / DELETE ; supprimer en POST ; rediriger après chaque action.beginTransaction(), commit(), rollBack().Vidéo · À regarder
ENPHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
$pdo->lastInsertId()| l'identifiant auto-incrémenté de la dernière insertion |
| Une ligne devient… |
|---|
| Accès |
|---|
PDO::FETCH_ASSOC | un tableau associatif | $ligne['nom'] |
PDO::FETCH_OBJ | un objet anonyme | $ligne->nom |
PDO::FETCH_NUM | un tableau indexé | $ligne[0] |
PDO::FETCH_CLASS | un objet de ta classe | $stmt->fetchAll(PDO::FETCH_CLASS, Stagiaire::class) |
PDO::FETCH_COLUMN | une seule colonne | liste simple de valeurs |
PDO::FETCH_KEY_PAIR | un tableau clé => valeur (2 colonnes) | [1 => 'Benali', 2 => 'Saidi'] |
Vidéo · À regarder
ENPHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
UPDATE |
PUT / PATCH |
modifier.php?id=3 |
| Delete | DELETE | DELETE | supprimer.php (en POST) |
Vidéo · À regarder
ENPHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
Le résumé liste les fonctions mysql_connect, mysql_query, mysql_create_db… Elles ont été retirées en PHP 7.0 (2015) : sur un serveur actuel, elles provoquent une erreur fatale « Call to undefined function ». Ce cours utilise PDO, comme le résumé le recommande ensuite.
PDO est une couche d'abstraction d'accès aux bases de données : la même façon d'écrire le code PHP fonctionne avec différents SGBD, seul le pilote change (pdo_mysql, pdo_pgsql, pdo_sqlite…). PDO fournit trois classes : PDO (la connexion), PDOStatement (une requête et son résultat) et PDOException (une erreur).
Le pilote doit être activé dans php.ini (ligne extension=pdo_mysql sans point-virgule devant). Avec XAMPP, il l'est déjà : vérifie-le dans phpinfo().
CREATE DATABASE gestion_stagiaires CHARACTER SET utf8mb4;
USE gestion_stagiaires;
CREATE TABLE stagiaires (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(50) NOT NULL,
prenom VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
filiere VARCHAR(10) NOT NULL,
date_inscription DATE NOT NULL DEFAULT (CURRENT_DATE)
);
INSERT INTO stagiaires (nom, prenom, email, filiere) VALUES
('Benali', 'Sara', 'sara.benali@ofppt-edu.ma', 'dev'),
('Saidi', 'Ali', 'ali.saidi@ofppt-edu.ma', 'infra'),
('Alaoui', 'Imane', 'imane.alaoui@ofppt-edu.ma', 'dev');
CREATE USER 'app_stagiaires'@'localhost' IDENTIFIED BY 'Stg#2026!app';
GRANT SELECT, INSERT, UPDATE, DELETE ON gestion_stagiaires.* TO 'app_stagiaires'@'localhost';<?php
function connexion(): PDO
{
static $pdo = null; // une seule connexion par requête HTTP
if ($pdo === null) {
$dsn = 'mysql:host=localhost;port=3306;dbname=gestion_stagiaires;charset=utf8mb4';
$pdo = new PDO($dsn, 'app_stagiaires', 'Stg#2026!app', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // une erreur SQL lance une exception
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // les lignes en tableaux associatifs
PDO::ATTR_EMULATE_PREPARES => false, // vraies requêtes préparées
]);
}
return $pdo;
}CRUD désigne les quatre opérations de base sur des données persistantes : Create (créer), Read (lire), Update (modifier), Delete (supprimer).
<?php
$pdo = connexion();
try {
$pdo->beginTransaction();
$pdo->prepare('INSERT INTO stagiaires (nom, prenom, email, filiere) VALUES (?, ?, ?, ?)')
->execute(['Filali', 'Hassan', 'hassan.filali@ofppt-edu.ma', 'dev']);
$id = $pdo->lastInsertId();
$stmt = $pdo->prepare('INSERT INTO inscriptions (id_stagiaire, module) VALUES (?, ?)');
foreach (['M105', 'M106', 'M107'] as $module) {
$stmt->execute([$id, $module]); // une requête préparée, exécutée plusieurs fois
}
$pdo->commit(); // tout est validé
} catch (Throwable $e) {
$pdo->rollBack(); // une erreur : rien n'est enregistré
throw $e;
}Assemble le CRUD complet de la section 5 : liste.php (avec filtre par filière, nombre de stagiaires, liens Voir / Modifier et bouton Supprimer, message de confirmation après chaque action), ajouter.php, voir.php, modifier.php (avec validation et gestion du doublon d'e-mail) et supprimer.php. Ajoute une pagination de 10 stagiaires par page avec LIMIT et OFFSET.
Ce script de recherche contient cinq problèmes (erreurs ou failles). Trouve-les et corrige le script.
<?php
$pdo = new PDO('mysql:host=localhost;dbname=gestion_stagiaires', 'root', '');
$nom = $_GET['nom'];
$res = $pdo->query("SELECT * FROM stagiaires WHERE nom LIKE '%$nom%'");
foreach ($res->fetch() as $s) {
echo "<p>$s[nom] $s[prenom]</p>";
}Écris voir.php?id=… qui affiche la fiche d'un stagiaire (nom, prénom, e-mail, filière, date d'inscription au format jj/mm/aaaa), ou une page 404 si l'identifiant n'existe pas ou n'est pas un entier. Ajoute un lien « Modifier » et un bouton « Supprimer ».
<?php
require __DIR__ . '/config/database.php';
try {
$pdo = connexion();
} catch (PDOException $e) {
error_log($e->getMessage()); // le détail va dans le journal
exit('Le service est momentanément indisponible.'); // le visiteur ne voit pas le détail
}$e->getMessage() peut contenir le nom du serveur, de la base ou de l'utilisateur MySQL. On l'enregistre dans un journal (error_log) et on affiche un message neutre. Le résumé utilise die("… " . $e->getMessage()) : c'est acceptable en développement seulement.
Se connecter à une base de données avec PDO (en anglais).
Les requêtes préparées
Une requête préparée est envoyée au serveur en deux temps : d'abord le modèle de la requête, avec des marqueurs à la place des valeurs (? ou :nom), puis les valeurs, séparément. Le SGBD ne peut donc jamais confondre une valeur avec du code SQL : c'est la protection contre l'injection SQL (chapitre 8).
<?php
$pdo = connexion();
// marqueurs ? : les valeurs dans l'ordre
$stmt = $pdo->prepare('SELECT * FROM stagiaires WHERE filiere = ? AND nom LIKE ?');
$stmt->execute(['dev', 'B%']);
// marqueurs nommés : plus lisibles quand il y a beaucoup de valeurs
$stmt = $pdo->prepare('INSERT INTO stagiaires (nom, prenom, email, filiere)
VALUES (:nom, :prenom, :email, :filiere)');
$stmt->execute([
'nom' => 'Tazi',
'prenom' => 'Youssef',
'email' => 'youssef.tazi@ofppt-edu.ma',
'filiere' => 'dev',
]);
echo 'Nouveau stagiaire n° ' . $pdo->lastInsertId();
// bindValue : préciser le type (utile pour LIMIT)
$stmt = $pdo->prepare('SELECT nom FROM stagiaires ORDER BY nom LIMIT :n');
$stmt->bindValue('n', 2, PDO::PARAM_INT);
$stmt->execute();$pdo->query("SELECT * FROM stagiaires WHERE nom = '" . $_GET['nom'] . "'") est une faille : si un visiteur envoie ' OR '1'='1, la requête renvoie toute la table, et d'autres valeurs peuvent même supprimer des données. Dès qu'une valeur vient de l'extérieur (formulaire, URL, cookie), utilise prepare() + execute().
L'exemple « Stockage des données dans MySQL » du résumé contient trois erreurs : la variable $dbname n'est pas définie (elle s'appelle $database), $connexionPDO>prepare oublie le tiret de ->, et bindParam('ss', $nom, $email) est la syntaxe de MySQLi, pas de PDO. La requête n'est d'ailleurs jamais exécutée. La version correcte : $stmt = $pdo->prepare('INSERT INTO users (nom, email) VALUES (?, ?)'); $stmt->execute([$nom, $email]);.
<?php
require __DIR__ . '/config/database.php';
$pdo = connexion();
$filiere = $_GET['filiere'] ?? '';
if ($filiere !== '') {
$stmt = $pdo->prepare('SELECT id, nom, prenom, email FROM stagiaires WHERE filiere = ? ORDER BY nom');
$stmt->execute([$filiere]);
} else {
$stmt = $pdo->query('SELECT id, nom, prenom, email FROM stagiaires ORDER BY nom');
}
$stagiaires = $stmt->fetchAll();
$total = $pdo->query('SELECT COUNT(*) FROM stagiaires')->fetchColumn();
?>
<h1>Stagiaires (<?= count($stagiaires) ?> / <?= $total ?>)</h1>
<table>
<tr><th>Nom</th><th>Prénom</th><th>E-mail</th></tr>
<?php foreach ($stagiaires as $s): ?>
<tr>
<td><?= htmlspecialchars($s['nom']) ?></td>
<td><?= htmlspecialchars($s['prenom']) ?></td>
<td><?= htmlspecialchars($s['email']) ?></td>
</tr>
<?php endforeach; ?>
</table>Sélectionner et afficher des données avec PHP (en anglais).
gestion_stagiaires/
├── config/database.php ← la connexion
├── liste.php ← Read : la liste (lien vers modifier et supprimer)
├── ajouter.php ← Create : formulaire + INSERT
├── modifier.php ← Update : formulaire prérempli + UPDATE
└── supprimer.php ← Delete : DELETE puis redirection<?php
require __DIR__ . '/config/database.php';
$filieres = ['dev' => 'Développement digital', 'infra' => 'Infrastructure digitale'];
$erreurs = [];
$s = ['nom' => '', 'prenom' => '', 'email' => '', 'filiere' => 'dev'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
foreach ($s as $champ => $v) {
$s[$champ] = trim($_POST[$champ] ?? '');
}
if ($s['nom'] === '' || $s['prenom'] === '') $erreurs[] = 'Nom et prénom obligatoires.';
if (!filter_var($s['email'], FILTER_VALIDATE_EMAIL)) $erreurs[] = 'E-mail invalide.';
if (!isset($filieres[$s['filiere']])) $erreurs[] = 'Filière inconnue.';
if (!$erreurs) {
try {
$stmt = connexion()->prepare(
'INSERT INTO stagiaires (nom, prenom, email, filiere) VALUES (?, ?, ?, ?)'
);
$stmt->execute([$s['nom'], $s['prenom'], $s['email'], $s['filiere']]);
header('Location: liste.php?message=ajout');
exit;
} catch (PDOException $e) {
if ($e->errorInfo[1] === 1062) { // 1062 : doublon sur une clé UNIQUE
$erreurs[] = 'Cet e-mail est déjà utilisé.';
} else {
throw $e;
}
}
}
}
?>
<?php foreach ($erreurs as $e): ?><p style="color:red"><?= $e ?></p><?php endforeach; ?>
<form method="post">
<input name="nom" value="<?= htmlspecialchars($s['nom']) ?>" placeholder="Nom">
<input name="prenom" value="<?= htmlspecialchars($s['prenom']) ?>" placeholder="Prénom">
<input name="email" type="email" value="<?= htmlspecialchars($s['email']) ?>" placeholder="E-mail">
<select name="filiere">
<?php foreach ($filieres as $code => $libelle): ?>
<option value="<?= $code ?>" <?= $s['filiere'] === $code ? 'selected' : '' ?>><?= $libelle ?></option>
<?php endforeach; ?>
</select>
<button>Ajouter</button>
</form><?php
require __DIR__ . '/config/database.php';
$pdo = connexion();
$id = (int) ($_GET['id'] ?? 0);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = $pdo->prepare('UPDATE stagiaires SET nom = ?, prenom = ?, email = ? WHERE id = ?');
$stmt->execute([trim($_POST['nom']), trim($_POST['prenom']), trim($_POST['email']), $id]);
header('Location: liste.php?message=modif');
exit;
}
$stmt = $pdo->prepare('SELECT * FROM stagiaires WHERE id = ?');
$stmt->execute([$id]);
$s = $stmt->fetch();
if (!$s) {
http_response_code(404);
exit('Stagiaire introuvable.');
}
?>
<form method="post">
<input name="nom" value="<?= htmlspecialchars($s['nom']) ?>">
<input name="prenom" value="<?= htmlspecialchars($s['prenom']) ?>">
<input name="email" type="email" value="<?= htmlspecialchars($s['email']) ?>">
<button>Enregistrer</button>
</form><?php
require __DIR__ . '/config/database.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { // on ne supprime jamais avec un simple lien GET
http_response_code(405);
exit('Méthode non autorisée.');
}
$stmt = connexion()->prepare('DELETE FROM stagiaires WHERE id = ?');
$stmt->execute([(int) ($_POST['id'] ?? 0)]);
header('Location: liste.php?message=' . ($stmt->rowCount() ? 'suppr' : 'introuvable'));
exit;<form method="post" action="supprimer.php" onsubmit="return confirm('Supprimer ce stagiaire ?')">
<input type="hidden" name="id" value="3">
<button>Supprimer</button>
</form>Un lien supprimer.php?id=3 peut être déclenché sans le vouloir : un robot qui suit les liens, une image piégée sur un autre site (<img src="…/supprimer.php?id=3">), le préchargement du navigateur. Les actions qui modifient des données passent par un formulaire POST (et, au chapitre 8, par un jeton CSRF).
Insérer, modifier et supprimer des données avec PHP (en anglais).