Aller au contenu principal

Module 8 – PHP & Base de données

Niveau 2 – PHP avancé


Objectifs

SQL de base (SELECT, INSERT, UPDATE, DELETE), connexion et requêtes avec PDO, requêtes préparées pour éviter l’injection SQL. On termine par un CRUD PHP natif sécurisé.


Théorie

SQL de base

  • SELECT : lire des lignes
    SELECT id, name FROM users WHERE active = 1
  • INSERT : ajouter une ligne
    INSERT INTO users (email, name) VALUES ('a@b.com', 'Doe')
  • UPDATE : modifier des lignes
    UPDATE users SET name = 'Martin' WHERE id = 1
  • DELETE : supprimer des lignes
    DELETE FROM users WHERE id = 1

Toujours filtrer avec WHERE sur UPDATE et DELETE pour ne pas modifier toute la table. Pour les identifiants, utiliser une clé primaire (ex. id).


PDO (PHP Data Objects)

PDO est l’extension standard pour accéder à une base depuis PHP (MySQL, PostgreSQL, SQLite, etc.). Elle fournit une interface unifiée et surtout les requêtes préparées.

Connexion :

$dsn = "mysql:host=localhost;dbname=app_db;charset=utf8mb4";
$user = "root";
$pass = "";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$pdo = new PDO($dsn, $user, $pass, $options);
  • ERRMODE_EXCEPTION : les erreurs SQL lèvent des exceptions (à attraper en try/catch).
  • FETCH_ASSOC : les résultats sont des tableaux associatifs (clés = noms des colonnes).

Ne jamais mettre de mot de passe en dur en production : utiliser des variables d’environnement ou un fichier de config non versionné.


Requêtes préparées (OBLIGATOIRE)

Une requête préparée envoie d’abord la structure de la requête au serveur, puis les valeurs séparément. Le serveur ne peut pas interpréter les valeurs comme du SQL → protection contre l’injection SQL.

Mauvaise pratique (DANGER) :

$email = $_GET["email"];
$stmt = $pdo->query("SELECT * FROM users WHERE email = '$email'");
// Si $email = "'; DROP TABLE users; --", c’est l’injection SQL.

Bonne pratique :

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(["email" => $email]);
$user = $stmt->fetch();

Les placeholders peuvent être nommés (:email) ou positionnels (?). Les valeurs sont passées à execute() et sont toujours traitées comme données, jamais comme du code SQL.


SELECT avec PDO

$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE id = :id");
$stmt->execute(["id" => $id]);
$user = $stmt->fetch(); // une ligne ou false
$users = $stmt->fetchAll(); // toutes les lignes

fetch() retourne une ligne (tableau associatif avec FETCH_ASSOC) ou false s’il n’y a plus de ligne.


INSERT avec PDO

$stmt = $pdo->prepare("INSERT INTO users (email, name, created_at) VALUES (:email, :name, NOW())");
$stmt->execute([
"email" => $email,
"name" => $name,
]);
$id = $pdo->lastInsertId(); // Inserted row ID (if auto_increment)

UPDATE et DELETE

$stmt = $pdo->prepare("UPDATE users SET name = :name WHERE id = :id");
$stmt->execute(["name" => $name, "id" => $id]);

$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute(["id" => $id]);

Toujours utiliser un identifiant (ou des critères contrôlés) dans le WHERE pour éviter de modifier/supprimer toute la table.


Sécurité : injection SQL

Injection SQL = un attaquant envoie du texte qui est concaténé dans la requête et interprété comme du SQL. Conséquences : lecture/suppression de données, contournement d’authentification.

Défense :

  • Toujours utiliser des requêtes préparées (PDO prepare + execute) pour toute valeur venant de l’utilisateur ou de l’application.
  • Ne jamais concaténer des variables dans une chaîne SQL.

Exemple CRUD complet (extrait)

// Create
$pdo->prepare("INSERT INTO users (email, name) VALUES (?, ?)")->execute([$email, $name]);

// Read one
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();

// Update
$pdo->prepare("UPDATE users SET name = ?, email = ? WHERE id = ?")->execute([$name, $email, $id]);

// Delete
$pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]);

Bonnes pratiques

  1. Requêtes préparées pour toutes les valeurs dynamiques.
  2. charset=utf8mb4 dans le DSN pour un bon support UTF-8.
  3. ERRMODE_EXCEPTION pour gérer les erreurs en try/catch.
  4. Ne jamais exposer les identifiants de BDD (fichier .env, hors dépôt).
  5. En production, limiter les privilèges du compte BDD (pas de DROP, etc.).

Quiz – Module 8

Q1. Pourquoi faut-il utiliser des requêtes préparées plutôt que concaténer des variables dans le SQL ?
Q2. Comment récupérer l’ID de la dernière ligne insérée (auto_increment) avec PDO ?
Q3. Que retourne $stmt->fetch() s’il n’y a plus de ligne ?
Q4. Quelle option PDO permet de lancer des exceptions en cas d’erreur SQL ?
Q5. Où doit-on stocker le mot de passe de la base en production ?

Réponses

R1. Pour éviter l’injection SQL : les valeurs sont envoyées séparément de la structure de la requête, le serveur ne les interprète pas comme du code SQL.

R2. $pdo->lastInsertId().

R3. false.

R4. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION.

R5. Dans des variables d’environnement ou un fichier de configuration non versionné (.env), jamais en dur dans le code.


Projet : CRUD PHP natif

Cahier des charges :
Une petite application (une table, ex. « articles » ou « contacts ») avec :

  • Create : formulaire + insertion avec requête préparée.
  • Read : liste des lignes + détail d’une ligne par ID.
  • Update : formulaire pré-rempli + mise à jour par ID.
  • Delete : suppression par ID (avec confirmation si possible).
  • Connexion PDO, uniquement requêtes préparées, gestion d’erreurs (try/catch), échappement HTML à l’affichage.

Critères de réussite : Aucune concaténation de variables dans le SQL ; code structuré (au moins séparation affichage / logique BDD si possible).


Suite

Module 9 – Architecture & bonnes pratiques PHP (MVC, SOLID, refactoring) puis Examen PHP avancé.