PHP Data Objects (PDO)
Dernière mise à jour le 24 novembre 2025
Création d'une base de données de test
Commençons par créer une base de données pour nos exemples.
Il n'y aura qu'une table contenant des données sur des pays. Ces données seront :
- le nom du pays;
- sa capitale;
- sa population;
- sa superficie (en km2).
Il y a de nombreuses façons (logiciels) de créer et de gérer une base de données MariaDB. Si vous êtes à l'aise avec le langage SQL, vous pouvez utiliser directement le client MariaDB sur le serveur.
On le lance de la façon suivante (on suppose que vous êtes l'usager MySQL "patoche") :
$ mysql -u patoche -p Enter password: mot_de_passe_de_patoche Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 133 Server version: 10.3.34-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04 Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]>
Vous pouvez alors entrer directement les commandes SQL pour gérer la base de données.
Les commandes SQL suivantes peuvent être copiées et collées dans l'interface du client MariaDB :
USE usagerXX;
CREATE TABLE pays (
nom VARCHAR(50) NOT NULL,
capitale VARCHAR(50) NOT NULL,
population INT NOT NULL,
superficie INT NOT NULL,
PRIMARY KEY (nom)
);
INSERT INTO pays (nom, capitale, population, superficie)
VALUES ('Algérie', 'Alger', '44487616', '2381741');
INSERT INTO pays (nom, capitale, population, superficie)
VALUES ('Cuba', 'La Havane', '11032343', '110860');
INSERT INTO pays (nom, capitale, population, superficie)
VALUES ('France', 'Paris', '66014000', '672051');
INSERT INTO pays (nom, capitale, population, superficie)
VALUES ('Gondor', 'Minas Tirith', '3198', '12173');
INSERT INTO pays (nom, capitale, population, superficie)
VALUES ('Japon', 'Tokyo', '124780000', '377975');
INSERT INTO pays (nom, capitale, population, superficie)
VALUES ('Québec', 'Québec', '8501833', '1542056');
Si tout a bien fonctionné on peut afficher le contenu de l'unique table :
MariaDB [usagerXX]> select * from pays; +----------+--------------+------------+-------------+ | nom | capitale | population | superficie | +----------+--------------+------------+-------------+ | Algérie | Alger | 44487616 | 2381741 | | Cuba | La Havane | 11032343 | 110860 | | France | Paris | 66014000 | 672051 | | Gondor | Minas Tirith | 3198 | 12173 | | Japon | Tokyo | 124780000 | 377975 | | Québec | Québec | 8501833 | 1542056 | +----------+--------------+------------+-------------+ 6 rows in set (0.001 sec) MariaDB [usagerXX]>
Dans le cas (peu probable) où vous ne seriez pas à l'aise avec le langage SQL, vous pouvez toujours utiliser l'application phpMyAdmin.
Connexion à la base de données
PHP Data Objects (ou simplement PDO) est une extension qui définit l'interface pour accéder à une base de données à l'aide de PHP.
Il s'agit d'une bibliothèque orientée objet.
PDO constitue une couche d'abstraction qui agit entre l'application PHP et un système de gestion de base de données (SGDB) tel que MySQL, MariaDB, SQLite, PostgreSQL, Oracle, etc.
Toute application utilisant une bases de données commence par la connexion à celle-ci.
Exemple :
$host = '...'; // 127.0.0.1 si la BD et l'application sont sur le même serveur $db = '...'; // nom de la base de données $user = '...'; $pass = '...'; $charset = 'utf8'; $dsn = "mysql:host=$host;dbname=$db;charset=$charset"; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $user, $pass, $options); // echo "Connexion établie"; } catch (\PDOException $e) { throw new \PDOException($e->getMessage(), (int)$e->getCode()); }
Remarques :
-
Le "data source name" ($dsn) ne doit pas contenir d'espaces.
-
Le même objet PDO doit être utilisé le plus souvent possible pour limiter le nombre de connexions à la base de données. Notez toutefois qu'il est détruit automatiquement à la fin de l'exécution d'un script. On peut aussi détruire explcitement un objet PDO en mettant la variable à
null.
Exécuter une requête
Il existe deux façons d'exécuter une requête avec PDO : la méthode PDO:query() et les requêtes préparées.
PDO::query()
Si aucune variable n'est utilisée dans la requête, on utilise généralement la méthode PDO:query().
Cette méthode exécute la requête et retourne un objet de la classe PDOStatement duquel on peut extraire le résultat.
La méthode fecth() retourne un enregistrement sous forme de tableau associatif.
Exemple :
$stmt = $pdo->query('SELECT nom FROM pays');
// tant qu'il y a des enregistrements (lignes)
while ($row = $stmt->fetch()) {
echo $row['nom'] . "<br>";
}
Sortie :
Cuba
...
Autres exemple :
$stmt = $pdo->query('SELECT * FROM pays');
while ($row = $stmt->fetch()) {
echo $row['capitale'] . " est la capitale d'un pays nommé " . $row['nom'] . "<br>";
}
Sortie :
La Havane est la capitale d'un pays nommé Cuba
...
Requêtes préparées
La principale raison pour laquelle on utilise les requêtes préparées est de se protéger des injections SQL. Ce sujet sera abordé dans un autre document.
Les requêtes préparées constituent en fait la seule bonne stratégie si une variable doit être incluse dans la requête.
Il s'agit tout simplement de remplacer celle-ci par un espace réservé (placeholder en anglais) et de fournir séparément à PDO la requête et la variable.
On utilise pour cela les méthodes prepare() et execute().
Imaginons que les variables $population et $superficie sont des données de formuulaire. Le code suivant :
$sql = 'SELECT * FROM pays WHERE population > $population AND superficie > $superficie';
peut être remplacé par :
// paramètres positionnels
$sql = 'SELECT * FROM pays WHERE population > ? AND superficie > ?';
ou encore par :
// paramètres nommés
$sql = 'SELECT * FROM pays WHERE population > :population AND superficie > :superficie';
Une requête incorporant des espaces réservés doit d'abord être préparée avec la méthode prepare(). Cette fonction retourne aussi un objet de la classe PDOStatement, mais sans aucune donnée.
Exemple :
$stmt = $pdo->prepare($sql);
Ensuite, la requête est exécutée avec la méthode execute() en lui passant les variables sous forme de tableau. Les données résultantes (si applicable) pourront ensuite être obtenues de l'objet PDOStatement.
Exemple :
// paramètres positionnels $pop = 1000000; // population de plus d'un million d'habitants $sup = 1500000; // superficie de plus d'un million et demie de km carrés $sql = 'SELECT nom FROM pays WHERE population > ? AND superficie > ?'; // étape 1 $stmt = $pdo->prepare($sql); // étape 2 $stmt->execute([$pop, $sup]); while ($row = $stmt->fetch()) { echo $row['nom'] . "<br>"; }
Sortie :
Québec
La même requête dans le client MariaDB :
MariaDB [monde]> SELECT * FROM pays WHERE population > 1000000 AND superficie > 1500000; +----------+----------+------------+------------+ | nom | capitale | population | superficie | +----------+----------+------------+------------+ | Algérie | Alger | 44487616 | 2381741 | | Québec | Québec | 8501833 | 1542056 | +----------+----------+------------+------------+ 2 rows in set (0.002 sec) MariaDB [monde]>
Ou encore :
// paramètres nommés ... $sql = 'SELECT nom FROM pays WHERE population > :population AND superficie > :superficie'; // étape 1 $stmt = $pdo->prepare($sql); // étape 2 $stmt->execute(['superficie' => $sup, 'population' => $pop]); ...
On remarque dans le dernier exemple que l'ordre des éléments du tableau passé à la méthode n'a pas d'importance puisqu'il s'agit d'un tableau associatif).
Boucler avec un foreach
Puisque la classe PDOStatement implémente l'interface Traversable, la façon la plus simle d'obtenir toutes les rangées est d'utiliser le foreach plutôt que la boucle while.
Exemple :
...
foreach ($stmt as $row) {
echo $row['nom'] . "<br>";
}
...
Obtenir les résultats avec fetch()
La méthode fetch() est très souple, puisqu'elle permet d'obtenir les résultats sous différentes formes :
| Paramètre | Type(s) retourné(s) |
|---|---|
| PDO::FETCH_NUM | tableau régulier |
| PDO::FETCH_ASSOC | tableau associatif |
| PDO::FETCH_BOTH | les deux types de tableaux |
| PDO::FETCH_OBJ | objet |
| PDO::FETCH_LAZY | tous les types précédents |
Exemple de récupération dans un tableau indexé numériquement (tableau ordinaire) :
$sql = "SELECT population, superficie FROM pays WHERE nom = ?"; $nom = "France"; // étape 1 $stmt = $pdo->prepare($sql); // étape 2 $stmt->execute([$nom]); $row = $stmt->fetch(PDO::FETCH_NUM); echo "Il y a en " . $nom . " " . $row[0] . " habitants sur " . $row[1] . "km<sup>2</sup><br>";
Sortie :
Avec un tableau associatif :
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo "Il y a en " . $nom . " " . $row['population']
. " habitants sur " . $row['superficie'] . "km<sup>2</sup><br>";
Avec les deux types de tableaux :
$row = $stmt->fetch(PDO::FETCH_BOTH);
echo "Il y a en " . $nom . " " . $row['population']
. " habitants sur " . $row['1'] . "km<sup>2</sup><br>";
Dans l'exemple précédent, il devient possible d'accéder simultanément aux éléments du tableau par un indice numérique ou par une clé (chaîne de caractères).
La constante PDO::FETCH_OBJ permet de créer un objet automatiquement à partir des valeurs obtenues. Nous y reviendrons plus tard.
Exemples de requêtes avec PDO
Pour terminer voici quelques exemples qui pourraient vous aider.
SELECT
Si la requête ne contient aucune variable, la méthode query() est suffisante.
Dans l'exemple suivant, on suppose qu'il s'agit d'un employé permanent si ce dernier travaille dans l'entreprise depuis au moins 5 ans ou q'il a été engagé avant 2010 :
function getEmployesPermanents() {
$pdo = getPdo();
$sql = "SELECT * FROM employe WHERE nb_annees >= 5 OR date_engagement < 2010";
$stmt = $pdo->query($sql);
return $stmt;
}
$resultat = getEmployesPermanents();
foreach ($resultat as $rangee) {
echo "$rangee['nom'], $rangee['prenom']<br>";
}
Si la requête contient une ou plusieurs variables, surtout en provenance d'un formulaire, une requête préparée devrait être utilisée pour se protéger des injections SQL.
// dangereux car on ne sait pas ce que contiennent vraiment les paramètres
$sql = "SELECT * FROM employe WHERE nb_annees >= $_POST['nb_annees'] OR < $_POST['date_engagement']";
Une requête préparée peut utiliser des paramètres positionnels ou nommés. Voyons d'abord un exemple avec des paramètres positionnels :
/// avec paramètres positionnels
function getEmployesPermanents($nbAnnees, $dateEngagement) {
$pdo = getPdo();
$sql = "SELECT * FROM employe WHERE nb_annees >= ? OR date_engagement < ?";
$stmt= $pdo->prepare($sql);
$stmt->execute([$nbAnnees, $dateEngagement]);
return $stmt;
}
Les requêtes préparées avec paramètres positionnels font un code un peu plus court, mais celles avec paramètres nommés ont l'avantage de ne pas accorder d'importance à l'ordre des paramètres :
/// avec paramètres nommés
function getEmployesPermanents($nbAnnees, $dateEngagement) {
$pdo = getPdo();
$sql = "SELECT * FROM employe WHERE nb_annees >= :annees OR date_engagement < :date";
$stmt= $pdo->prepare($sql);
$stmt->execute(['date' => $dateEngagement, 'annees' => $nbAnnees]);
return $stmt;
}
INSERT
function ajouter($nom, $prenom) {
$pdo = getPdo();
try {
$sql = "INSERT INTO employe (nom, prenom) VALUES (?, ?)";
$stmt= $pdo->prepare($sql);
$stmt->execute([$nom, $prenom]);
} catch (Exception $e) {
echo "Erreur...";
exit;
}
}
UPDATE
function modifier($id, $nom, $prenom) {
$pdo = getPdo();
try {
$sql = "UPDATE employe SET nom=?, prenom=? WHERE id=?";
$stmt= $pdo->prepare($sql);
$stmt->execute([$nom, $prenom, $id]);
} catch (Exception $e) {
echo "Erreur...";
exit;
}
}
DELETE
function supprimer($id) {
$pdo = getPdo();
$sql = "DELETE FROM employe WHERE id=?";
$stmt= $pdo->prepare($sql);
$stmt->execute([$id]);
}
Sauvegarder et restaurer une base de données
Sauvegarder et restaurer une base de données MySQL est très simple. Vous trouverez ici l'essentiel de ce qu'il faut savoir.
Sauvegarder
La syntaxe de la commande mysqldump est la suivante :
mysqldump -u usager -p base_de_données > fichier_de_sauvegarde
Dans l'exemple suivant, nous sauvegardons la totalité (structure et données) de la base de données "lotr". Il est possible de sauvegarder seulement les données, seulement une table, etc., mais en général nous voulons tout sauvegarder de façon à pouvoir ensuite réinstaller notre serveur à partir de zéro.
Notez bien que l'opération se fait au niveau du shell et qu'il n'est pas nécessaire "d'entrer" comme tel dans le client MariaDB.
Exemple :
$ sudo mysqldump -u patoche -p lotr > lotr_2022-11-04.sql
Le suffixe .sql du fichier n'est pas obligatoire, mais nous renseigne sur son contenu.
N'oubliez pas ensuite de télécharger le fichier de sauvegared sur autre ordinateur!
Pour une seule table il faut spécifier le nom de la base de données et ensuite celui de la table à sauvegarder :
$ sudo mysqldump -u patoche -p lotr personnage > table_personnage.sql
Restaurer
Avant de restaurer une base de données, vous devez la détruire sur le serveur, puis la recréer :
MariaDB [(none)]> drop database lotr; Query OK, 24 rows affected (0.35 sec) MariaDB [(none)]> create database lotr; Query OK, 1 row affected (0.01 sec) MariaDB [(none)]> exit; Bye $
On procède ensuite directement avec la commande mysql. Exemple :
$ sudo mysql -u patoche -p lotr < lotr_2022-11-04.sql
Et voilà, ce n'est pas plus compliqué!
Exercice dirigé
Le but de cet exercice, que nous réaliserons en classe, est de nous familiariser avec PDO dans le cadre de notre projet de session (annonces classées).
Étape 1
Connectez-vous à votre base de données (avec le client MariaDB ou phpMyAdmin) et créez les tables nécessaires.
Étape 2
Avec Visual Studio Code (pas besoin de créer un nouveau projet, conservez votre projet courant), créez le fichier bd.php. Ce fichier contiendra toutes les fonctions contenant des requêtes à la base de données.
En vous référant à cette section du tutoriel, écrivez la fonction get_pdo() qui retourne une connexion à la bases de données (une instance de la classe PDO) ou false en cas d'échec.
Solution :
<?php
// ----------------------------------------------------------------------------
// Retourne une instance de la classe PDO pour accéder à une base de données.
// ----------------------------------------------------------------------------
function get_pdo():bool|PDOStatement {
$host = '127.0.0.1';
$db = 'usagerXX';
$user = 'usagerXX';
$pass = '........';
$charset = 'utf8';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
$pdo = false;
//throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
return $pdo;
}
Étape 3
Créez maintenant le fichier test_bd.php qui nous servira à faire nos tests.
Dans ce fichier, écrivez le code nésessaire pour ouvrir une connexion à la base de données et vous assurer du succès de l'opération .
Solution :
<?php
require "bd.php";
echo "<h2>Connexion à la base de données</h2>";
$pdo = get_pdo();
if ($pdo) {
echo "<p>Connexion réussie</p>";
} else {
echo "<p>Houston, we have a problem;";
}
Étape 4
Nous allons maintenant tester l'insertion. Ajoutez au fichier bd.php la fonction suivante :
function ajouter_article($categorie, $usager, $titre, $description, $prix,
$negociable, $chemin, $date): bool {
...
}
La fonction retourne true ou false pour indiquer le succès ou l'échec de l'opération. Utilisez une requête préparée pour plus de sécurité
Solution :
// ---------------------------------------------------------------------------- // Ajoute un article. Retourne true ou false pour indiquer le succès ou l'échec // de l'opération. // ---------------------------------------------------------------------------- function ajouter_article($categorie, $usager, $titre, $description, $prix, $negociable, $chemin, $date) { $retour = true; $sql = "INSERT INTO article (id_categorie, id_usager, titre, description, prix, negociable, chemin_image, date_pub) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"; try { $pdo = get_pdo(); $stmt= $pdo->prepare($sql); $stmt->execute([$categorie, $usager, $titre, $description, $prix, $negociable, $chemin, $date]); } catch (Exception $e) { //echo $e->getMessage(); //exit; $retour = false; } return $retour; }
Étape 5
Ajoutez au fichier test_bd.php le code nécessaire pour tester la fonction précédente.
Solution :
echo "<h2>Ajout d'un article</h2>";
$id_categorie = 1;
$id_usager = 1;
$titre = "Un titre";
$description = "Une descriptnio";
$prix = 100;
$negociable = 0; // 0 ou 1, pas true ou false
$chemin_image = null;
$date_pub = date('Y-m-d H:i:s'); // date courante
$succes = ajouter_article($id_categorie, $id_usager, $titre, $description,
$prix, $negociable, $chemin_image, $date_pub);
if ($succes) {
echo "Article ajouté";
} else {
echo "Une erreur s'est prpduite";
}
Étape 6
Nous allons pour terminer par afficher tous les articles de la table. Pour cela il faut ajouter au fichier bd.php la fonction suivante :
function obtenir_articles(): bool|PDOStatement {
...
}
La fonction retourne un tableau à deux dimensions contenant tous les articles ou false en cas d'erreur.
Solution :
// ---------------------------------------------------------------------------- // Retourne tous les articles ou false. // ---------------------------------------------------------------------------- function obtenir_articles() { $sql = "SELECT * FROM article"; try { $pdo = get_pdo(); $stmt = $pdo->query($sql); $retour = $stmt; } catch (Exception $e) { //echo $e->getMessage(); //exit; $retour = false; } return $retour; }
Étape 7
Ajoutez au fichier test_bd.php le code nécessaire pour tester la fonction précédente (afficher tous les articles de la table).
Solution :
echo "<h2>Affichage des articles</h2>";
$articles = obtenir_articles();
foreach ($articles as $article) {
echo $article['titre'] ."<br>";
echo $article['prix'] . " $<hr>";
// ...
}
Questions et exercices
Nous ne fournirons pas les réponses à ces questions pour deux bonnes raisons. La première est que celles-ci se trouvent dans les notes de cours. La seconde est que, dans certains cas, ce serait presque donner la réponse à des questions d'examen.
Question 1
Expliquez à quel moment il est préférable d'utiliser les méthodes query(), prepare() ou execute().
Question 2
Écrivez le code d'une méthode qui effectue un "select" avec des paramètres positionnels.
Question 3
Écrivez à nouveau la méthode de la question précédente, cette fois avec des paramètres nommés.
Question 4
Savez-vous comment vous connecter au client MariaDB au serveur dans une console? Vous devriez!
Question 5
Donnez la différence entre les trois appels suivants à la méthode fetch() :
$stmt->fetch(PDO::FETCH_NUM); $stmt->fetch(PDO::FETCH_ASSOC); $stmt->fetch(PDO::FETCH_BOTH);
Question 6
Donnez le code que l'usager MySQL "bob" devra écrire dans la console pour sauvegarder sa base de données "ma_bd".
Question 7
Donnez le code que Bob devra écrire dans la console pour restaurer sa base de données.
Question 8
Expliquez en quelques mots ce qu'est une injection SQL.