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