π PDO β PHP Data Objects
PDO est l'interface PHP standard pour accΓ©der aux bases de donnΓ©es. Elle supporte MySQL, SQLite, PostgreSQL et plus, avec une API unifiΓ©e.
PHP
PDO::ERRMODE_EXCEPTION, // lève des exceptions
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // tableaux associatifs
PDO::ATTR_EMULATE_PREPARES => false, // types natifs (int, float)
];
try {
$pdo = new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASS'], $options);
} catch (PDOException $e) {
// Ne jamais afficher $e->getMessage() Γ l'utilisateur en production
error_log($e->getMessage());
throw new RuntimeException('Erreur de connexion', 0, $e);
}
Ne stockez jamais les identifiants de connexion en dur dans le code. Utilisez des variables d'environnement (
$_ENV) ou un fichier .env hors de la racine web et hors du dΓ©pΓ΄t Git.βοΈ CRUD avec PDO
PHP
query('SELECT * FROM produits ORDER BY nom');
$produits = $stmt->fetchAll(); // tableau de tableaux associatifs
// Un produit par id (avec paramètre)
$stmt = $pdo->prepare('SELECT * FROM produits WHERE id = :id');
$stmt->execute([':id' => $id]);
$produit = $stmt->fetch(); // false si non trouvΓ©
// ββ CREATE βββββββββββββββββββββββββββββββββββββββββββββββ
$stmt = $pdo->prepare(
'INSERT INTO produits (nom, prix, stock, categorie_id)
VALUES (:nom, :prix, :stock, :cat)'
);
$stmt->execute([
':nom' => $nom,
':prix' => $prix,
':stock' => $stock,
':cat' => $categorie_id,
]);
$newId = (int) $pdo->lastInsertId();
// ββ UPDATE βββββββββββββββββββββββββββββββββββββββββββββββ
$stmt = $pdo->prepare('UPDATE produits SET prix = :prix, stock = :stock WHERE id = :id');
$stmt->execute([':prix' => $prix, ':stock' => $stock, ':id' => $id]);
$nbModifies = $stmt->rowCount();
// ββ DELETE βββββββββββββββββββββββββββββββββββββββββββββββ
$stmt = $pdo->prepare('DELETE FROM produits WHERE id = :id');
$stmt->execute([':id' => $id]);
// ββ Comptage βββββββββββββββββββββββββββββββββββββββββββββ
$stmt = $pdo->prepare('SELECT COUNT(*) FROM produits WHERE categorie_id = :cat');
$stmt->execute([':cat' => $catId]);
$count = (int) $stmt->fetchColumn();
π Transactions PDO
PHP
beginTransaction();
// CrΓ©er la commande
$stmt = $pdo->prepare('INSERT INTO commandes (client_id, total) VALUES (:cid, 0)');
$stmt->execute([':cid' => $clientId]);
$cmdId = (int) $pdo->lastInsertId();
$total = 0;
$stmtLigne = $pdo->prepare(
'INSERT INTO commande_produits (commande_id, produit_id, quantite, prix_unitaire)
VALUES (:cmd, :prod, :qty, :prix)'
);
$stmtStock = $pdo->prepare('UPDATE produits SET stock = stock - :qty WHERE id = :id AND stock >= :qty');
foreach ($produits as $item) {
// RΓ©duire le stock (vΓ©rifie que stock >= quantitΓ©)
$stmtStock->execute([':qty' => $item['quantite'], ':id' => $item['id']]);
if ($stmtStock->rowCount() === 0) {
throw new RuntimeException("Stock insuffisant pour le produit #{$item['id']}");
}
$stmtLigne->execute([
':cmd' => $cmdId,
':prod' => $item['id'],
':qty' => $item['quantite'],
':prix' => $item['prix'],
]);
$total += $item['quantite'] * $item['prix'];
}
// Mettre Γ jour le total
$pdo->prepare('UPDATE commandes SET total = :total WHERE id = :id')
->execute([':total' => round($total, 2), ':id' => $cmdId]);
$pdo->commit();
return $cmdId;
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
}
π’ mysql2 β Node.js
mysql2 est le driver MySQL le plus utilisΓ© en Node.js. Il supporte les Promises, les requΓͺtes prΓ©parΓ©es et les pools de connexions.
Node.js
// npm install mysql2
const mysql = require('mysql2/promise');
// Pool de connexions (recommandΓ© en production)
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
database: 'formation_mysql',
charset: 'utf8mb4',
waitForConnections: true,
connectionLimit: 10, // max connexions simultanΓ©es
queueLimit: 0, // 0 = pas de limite d'attente
});
// Test de connexion
async function testConnexion() {
const [rows] = await pool.query('SELECT 1 AS ok');
console.log('MySQL connectΓ© :', rows[0]);
}
testConnexion();
βοΈ CRUD avec mysql2
Node.js
// ββ READ βββββββββββββββββββββββββββββββββββββββββββββββββ
async function getProduits(categorieId = null) {
let sql = 'SELECT p.*, c.nom AS categorie FROM produits p LEFT JOIN categories c ON p.categorie_id = c.id';
const params = [];
if (categorieId !== null) {
sql += ' WHERE p.categorie_id = ?';
params.push(categorieId);
}
const [rows] = await pool.query(sql, params);
return rows;
}
async function getProduit(id) {
const [rows] = await pool.query('SELECT * FROM produits WHERE id = ?', [id]);
return rows[0] || null;
}
// ββ CREATE βββββββββββββββββββββββββββββββββββββββββββββββ
async function creerProduit({ nom, prix, stock, categorieId }) {
const [result] = await pool.query(
'INSERT INTO produits (nom, prix, stock, categorie_id) VALUES (?, ?, ?, ?)',
[nom, prix, stock, categorieId]
);
return result.insertId;
}
// ββ UPDATE βββββββββββββββββββββββββββββββββββββββββββββββ
async function majProduit(id, { nom, prix, stock }) {
const [result] = await pool.query(
'UPDATE produits SET nom = ?, prix = ?, stock = ? WHERE id = ?',
[nom, prix, stock, id]
);
return result.affectedRows;
}
// ββ DELETE βββββββββββββββββββββββββββββββββββββββββββββββ
async function supprimerProduit(id) {
const [result] = await pool.query('DELETE FROM produits WHERE id = ?', [id]);
return result.affectedRows;
}
// ββ Transaction ββββββββββββββββββββββββββββββββββββββββββ
async function passerCommande(clientId, produits) {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
const [res] = await conn.query('INSERT INTO commandes (client_id, total) VALUES (?, 0)', [clientId]);
const cmdId = res.insertId;
let total = 0;
for (const item of produits) {
const [upd] = await conn.query(
'UPDATE produits SET stock = stock - ? WHERE id = ? AND stock >= ?',
[item.quantite, item.id, item.quantite]
);
if (upd.affectedRows === 0) throw new Error(`Stock insuffisant pour produit #${item.id}`);
await conn.query(
'INSERT INTO commande_produits (commande_id, produit_id, quantite, prix_unitaire) VALUES (?, ?, ?, ?)',
[cmdId, item.id, item.quantite, item.prix]
);
total += item.quantite * item.prix;
}
await conn.query('UPDATE commandes SET total = ? WHERE id = ?', [total.toFixed(2), cmdId]);
await conn.commit();
return cmdId;
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release(); // TOUJOURS libΓ©rer la connexion
}
}
π‘οΈ PrΓ©venir l'injection SQL
-- Attaque par injection SQL
-- Si le code PHP fait : "SELECT * FROM users WHERE email = '" . $email . "'"
-- et que l'utilisateur entre : alice@mail.fr' OR '1'='1
-- La requΓͺte devient : SELECT * FROM users WHERE email = 'alice@mail.fr' OR '1'='1'
-- => retourne TOUS les utilisateurs !
PHP
query("SELECT * FROM produits WHERE nom = '$nom'"); // DANGER
// BON : requΓͺte prΓ©parΓ©e β les paramΓ¨tres sont des donnΓ©es, jamais du code
$stmt = $pdo->prepare('SELECT * FROM produits WHERE nom = :nom');
$stmt->execute([':nom' => $nom]); // SQL et donnΓ©es sΓ©parΓ©s
Node.js
// MAUVAIS
const sql = `SELECT * FROM produits WHERE nom = '${nom}'`; // DANGER
pool.query(sql);
// BON : placeholders ? toujours
pool.query('SELECT * FROM produits WHERE nom = ?', [nom]);
Ne faites jamais confiance aux donnΓ©es entrΓ©es par un utilisateur. Toujours utiliser des requΓͺtes prΓ©parΓ©es avec des placeholders (
? ou :param) pour passer les valeurs externes.