0 XP
?
Intermédiaire50 min35 XP

Objectifs de cette leçon

  • Se connecter à MySQL avec PDO
  • Exécuter des requêtes préparées
  • Comprendre la sécurité anti-injection SQL

PHP et Bases de Données 🗄️

PHP excelle pour interagir avec les bases de données. Aujourd'hui, on utilise PDO (PHP Data Objects) — l'abstraction de database recommandée.

Pourquoi PDO ?

┌─────────────────────────────────────────┐
│           Ton script PHP                │
│              ↓                          │
│        PDO (couche abstraction)         │
│     ┌──────┼──────┐                     │
│  MySQL   SQLite  PostgreSQL            │
│  driver   driver   driver              │
└─────────────────────────────────────────┘
CritèremysqliPDO
SupportéMySQL uniquement12+ SGBD
Requêtes préparéesOuiOui
Objet-orientedPartiellementOui
Facilité d'apprentissageMoyenFacile

Connexion à MySQL

<?php
// db.php — Connexion PDO

$host = "localhost";
$dbname = "ma_base";
$username = "root";
$password = "";

try {
    $pdo = new PDO(
        "mysql:host=$host;dbname=$dbname;charset=utf8mb4",
        $username,
        $password,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
} catch (PDOException $e) {
    die("Erreur de connexion : " . $e->getMessage());
}
?>

💡 Astuce : ERRMODE_EXCEPTION lance une exception en cas d'erreur — c'est indispensable pour le debug.

Requêtes préparées (INSERT)

<?php
// Ajouter un utilisateur
$stmt = $pdo->prepare("
    INSERT INTO users (nom, email, mdp_hash, created_at)
    VALUES (:nom, :email, :mdp, NOW())
");

$stmt->execute([
    ':nom' => "Alice Dupont",
    ':email' => "alice@example.com",
    ':mdp' => password_hash("monMdp123", PASSWORD_DEFAULT)
]);

echo "Utilisateur créé avec l'ID : " . $pdo->lastInsertId();
?>

⚠️ Sécurité : Les requêtes préparées empêchent les injections SQL. Ne JAMAIS insérer les données directement dans la requête.

Requêtes préparées (SELECT)

<?php
// Récupérer un seul utilisateur
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute([':email' => 'alice@example.com']);
$user = $stmt->fetch();

if ($user) {
    echo $user['nom'];      // Alice Dupont
    echo $user['email'];    // alice@example.com
}

// Récupérer plusieurs résultats
$stmt = $pdo->query("SELECT * FROM users ORDER BY created_at DESC");
$users = $stmt->fetchAll();

foreach ($users as $u) {
    echo $u['nom'] . "\n";
}

// Compter les résultats
$count = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
echo "Total : $count utilisateurs";
?>

Update et Delete

<?php
// Mettre à jour
$stmt = $pdo->prepare("
    UPDATE users
    SET nom = :nom, email = :email
    WHERE id = :id
");
$stmt->execute([
    ':nom' => "Alice Martin",
    ':email' => "alice.martin@example.com",
    ':id' => 1
]);
echo $stmt->rowCount() . " ligne(s) modifiée(s)";

// Supprimer
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute([':id' => 99]);
echo $stmt->rowCount() . " ligne(s) supprimée(s)";
?>

Transactions

<?php
try {
    $pdo->beginTransaction();

    // Transfert bancaire : débiter A, créditer B
    $stmt = $pdo->prepare("UPDATE comptes SET solde = solde - :montant WHERE id = :id");
    $stmt->execute([':montant' => 100, ':id' => 1]);

    $stmt = $pdo->prepare("UPDATE comptes SET solde = solde + :montant WHERE id = :id");
    $stmt->execute([':montant' => 100, ':id' => 2]);

    $pdo->commit();
    echo "Transfert réussi !";
} catch (Exception $e) {
    $pdo->rollBack();
    echo "Transfert annulé : " . $e->getMessage();
}
?>

Modèle de données complet

<?php
class UserModel {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function findById(int $id): ?array {
        $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch() ?: null;
    }

    public function findByEmail(string $email): ?array {
        $stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?");
        $stmt->execute([$email]);
        return $stmt->fetch() ?: null;
    }

    public function create(string $nom, string $email, string $mdp): int {
        $stmt = $this->pdo->prepare("
            INSERT INTO users (nom, email, mdp_hash, created_at)
            VALUES (?, ?, ?, NOW())
        ");
        $stmt->execute([$nom, $email, password_hash($mdp, PASSWORD_DEFAULT)]);
        return (int)$this->pdo->lastInsertId();
    }

    public function update(int $id, array $data): bool {
        $champs = implode(', ', array_map(fn($k) => "$k = ?", array_keys($data)));
        $stmt = $this->pdo->prepare("UPDATE users SET $champs WHERE id = ?");
        return $stmt->execute([...array_values($data), $id]);
    }

    public function delete(int $id): bool {
        $stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
        return $stmt->execute([$id]);
    }

    public function list(int $page = 1, int $parPage = 20): array {
        $offset = ($page - 1) * $parPage;
        $stmt = $this->pdo->prepare("
            SELECT * FROM users
            ORDER BY created_at DESC
            LIMIT ? OFFSET ?
        ");
        $stmt->execute([$parPage, $offset]);
        return $stmt->fetchAll();
    }
}

// Utilisation
$userModel = new UserModel($pdo);
$nouvelId = $userModel->create("Bob", "bob@example.com", "secret123");
$bob = $userModel->findById($nouvelId);
?>

Migration de la base de données

<?php
// migration.php — Créer les tables
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nom VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    mdp_hash VARCHAR(255) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)");

$pdo->exec("CREATE TABLE IF NOT EXISTS articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    titre VARCHAR(255) NOT NULL,
    contenu TEXT,
    slug VARCHAR(255) UNIQUE,
    published BOOLEAN DEFAULT FALSE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)");
?>

Comparaison avec JavaScript/Node.js

OpérationNode.js (mysql2)PHP (PDO)
ConnexioncreatePool()new PDO()
Requêtepool.query()$pdo->query()
Preparedpool.execute()$pdo->prepare() + execute()
Transactionconn.beginTransaction()$pdo->beginTransaction()

🎯 A retenir : Toujours utiliser PDO avec des requêtes préparées. Ne jamais faire confiance aux entrées utilisateur. Valide, assainis, puis exécute.


Prochaine leçon : Projet final PHP — construis une application complète !

⚡ Simulateur — BDD avec PDO

Étape 1/7
📝 Pseudo-code
1<?php
2$pdo = new PDO("mysql:host=localhost;dbname=app", "root", "");
3$stmt = $pdo->query("SELECT * FROM users");
4while ($user = $stmt->fetch()) {
5 echo $user["nom"];
6}
7?>
⚙️ Exécution
📦 Mémoire
Aucune variable
🔀 Flux d'exécution
<?php
3
Début du script

Ouverture du bloc PHP.