Aller au contenu

PHP : bases de données avec PDO

PDO (PHP Data Objects) est l’interface commune d’accès aux bases de données. Les exemples reprennent la base fictive boutique (tables clients et commandes).

Fenêtre de terminal
sudo apt install php-mysql # MariaDB et MySQL
sudo apt install php-pgsql # PostgreSQL
sudo apt install php-sqlite3 # SQLite
php -m | grep -i pdo

Recharger PHP-FPM après l’installation (sudo systemctl reload php8.3-fpm).

<?php
declare(strict_types=1);
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=boutique;charset=utf8mb4',
'app',
getenv('DB_PASSWORD'),
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
);
Base DSN
MariaDB / MySQL mysql:host=127.0.0.1;dbname=boutique;charset=utf8mb4
PostgreSQL pgsql:host=127.0.0.1;port=5432;dbname=boutique
SQLite sqlite:/var/www/monapp/donnees/boutique.db

Le mot de passe provient de l’environnement, jamais du code. Créer le compte app avec des droits limités : voir MariaDB : utilisateurs et droits ou PostgreSQL : utilisateurs et droits.

<?php
$stmt = $pdo->prepare('SELECT id, nom, email FROM clients WHERE email = :email');
$stmt->execute(['email' => $email]);
$client = $stmt->fetch(); // une ligne ou false
$stmt = $pdo->prepare('SELECT * FROM commandes WHERE client_id = ? AND total > ?');
$stmt->execute([$clientId, 100]);
$commandes = $stmt->fetchAll(); // toutes les lignes

Les noms de tables et de colonnes ne se lient pas : les valider contre une liste autorisée.

<?php
$stmt = $pdo->prepare('INSERT INTO clients (nom, email) VALUES (:nom, :email)');
$stmt->execute(['nom' => 'Alice', 'email' => 'alice@exemple.com']);
$id = $pdo->lastInsertId();
$stmt = $pdo->prepare('UPDATE clients SET actif = :actif WHERE id = :id');
$stmt->execute(['actif' => 0, 'id' => $id]);
echo $stmt->rowCount(), " ligne(s)\n";
<?php
try {
$pdo->beginTransaction();
$pdo->prepare('UPDATE commandes SET statut = ? WHERE id = ?')->execute(['payee', 10]);
$pdo->prepare('INSERT INTO journal (message) VALUES (?)')->execute(['commande 10 payée']);
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}

Avec PDO::ERRMODE_EXCEPTION, une erreur SQL lève une PDOException :

<?php
try {
$pdo->query('SELECT * FROM table_inexistante');
} catch (PDOException $e) {
error_log($e->getMessage()); // dans le journal, pas dans la page
http_response_code(500);
echo 'Erreur interne.';
}

Ne pas afficher le message SQL aux visiteurs.

<?php
$stmt = $pdo->query('SELECT id, nom FROM clients');
while ($ligne = $stmt->fetch()) {
echo $ligne['nom'], "\n";
}

Parcourir ligne par ligne évite de charger toute la table en mémoire (contrairement à fetchAll).

<?php
$pdo = new PDO('sqlite:/var/www/monapp/donnees/boutique.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('PRAGMA journal_mode = WAL');
$pdo->exec('PRAGMA foreign_keys = ON');

Le dossier et le fichier doivent être modifiables par l’utilisateur de PHP-FPM (SQLite crée des fichiers annexes à côté de la base). Voir SQLite : installation.

Symptôme Piste
could not find driver Extension manquante (php-mysql, php-pgsql, php-sqlite3) ou PHP-FPM non rechargé
SQLSTATE[HY000] [2002] Connection refused Serveur arrêté, mauvais hôte ou port
SQLSTATE[HY000] [1045] Access denied Identifiants ou hôte du compte incorrects
SQLSTATE[HY000] [2002] No such file or directory Avec localhost, PHP utilise un socket : utiliser 127.0.0.1 ou indiquer unix_socket=
attempt to write a readonly database (SQLite) Droits sur le dossier ou le fichier
SQLSTATE[23000] Duplicate entry Violation d’une contrainte unique

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

PDO fonctionne de la même manière en PHP 8.3 et 8.5. Sur 26.04, le serveur fourni est MariaDB 11.8 (voir MariaDB : installation) : le pilote php-mysql s’y connecte sans changement.