La pièce était silencieuse, juste le ronronnement des serveurs. Sur l'écran, des lignes de code s'alignaient comme des dossiers dans un coffre. MySQL. Une base. Des tables. Des requêtes qui parlaient un langage étrange. En PHP, on y accédait avec PDO. Un passe-partout sécurisé. Parce qu'une requête mal tournée, c'était comme forcer un coffre. Ça pouvait tout faire sauter.
Connexion PDO - Établir le Contact
<?php
// connexion.php
$serveur = "localhost";
$utilisateur = "marlowe";
$motdepasse = "N3tW0rk47";
$basededonnees = "archives_sombres";
try {
$pdo = new PDO(
"mysql:host=$serveur;dbname=$basededonnees;charset=utf8",
$utilisateur,
$motdepasse,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
echo "▪ Connexion établie au réseau.\n";
} catch (PDOException $e) {
error_log("Erreur connexion : " . $e->getMessage());
die("Impossible d'accéder aux archives.");
}
?>
CRUD - Les Quatre Opérations
<?php
// crud.php
require_once 'connexion.php';
class DossierHandler {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
// CREATE - Classer un nouveau dossier
public function creerDossier($nom, $categorie, $urgence) {
$sql = "INSERT INTO dossiers (nom, categorie, urgence, date_creation)
VALUES (:nom, :categorie, :urgence, NOW())";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([
':nom' => $nom,
':categorie' => $categorie,
':urgence' => $urgence
]);
}
// READ - Consulter les dossiers
public function lireDossiers($categorie = null) {
$sql = "SELECT id, nom, categorie, urgence, date_creation
FROM dossiers WHERE statut = 'actif'";
$params = [];
if ($categorie) {
$sql .= " AND categorie = :categorie";
$params[':categorie'] = $categorie;
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
// UPDATE - Mettre à jour un dossier
public function mettreAJourDossier($id, $champs) {
$updates = [];
$params = [':id' => $id];
foreach ($champs as $cle => $valeur) {
$updates[] = "$cle = :$cle";
$params[":$cle"] = $valeur;
}
$sql = "UPDATE dossiers SET " . implode(', ', $updates) . " WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($params);
}
// DELETE - Archiver un dossier
public function archiverDossier($id) {
$sql = "UPDATE dossiers SET statut = 'archive' WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([':id' => $id]);
}
}
// Utilisation
$handler = new DossierHandler($pdo);
// Nouveau dossier sensible
$handler->creerDossier('Opération Nightfall', 'confidentiel', 'haute');
// Consultation
$dossiers = $handler->lireDossiers('confidentiel');
foreach ($dossiers as $dossier) {
echo "Dossier #{$dossier['id']}: {$dossier['nom']} - Urgence: {$dossier['urgence']}\n";
}
?>
Injection SQL - La Porte Dérobée
<?php
// injection.php
require_once 'connexion.php';
// NE JAMAIS FAIRE ÇA
$inputUtilisateur = $_POST['recherche'] ?? '';
$sqlDangereux = "SELECT * FROM agents WHERE nom = '$inputUtilisateur'";
// Si inputUtilisateur = "' OR '1'='1", la requête devient:
// SELECT * FROM agents WHERE nom = '' OR '1'='1'
// -> Retourne TOUS les agents
// FAÇON SÉCURISÉE
function rechercherAgentSecurise($pdo, $nom) {
$sql = "SELECT * FROM agents WHERE nom = :nom AND actif = 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([':nom' => $nom]);
return $stmt->fetch();
}
// Exemple: même avec une tentative d'injection
$resultat = rechercherAgentSecurise($pdo, "' OR '1'='1");
// Cherche littéralement un agent avec le nom "' OR '1'='1"
// Retourne faux car aucun agent ne porte ce nom
echo "Recherche sécurisée : " . ($resultat ? "Trouvé" : "Non trouvé") . "\n";
?>
Transactions - Opérations Atomiques
<?php
// transactions.php
require_once 'connexion.php';
class TransfertHandler {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function transfererDocument($docId, $sourceId, $destinationId) {
try {
$this->pdo->beginTransaction();
// 1. Vérifier que le document existe chez la source
$sql1 = "SELECT * FROM documents WHERE id = :id AND emplacement = :source";
$stmt1 = $this->pdo->prepare($sql1);
$stmt1->execute([':id' => $docId, ':source' => $sourceId]);
if (!$stmt1->fetch()) {
throw new Exception("Document introuvable à la source");
}
// 2. Mettre à jour l'emplacement
$sql2 = "UPDATE documents SET emplacement = :dest WHERE id = :id";
$stmt2 = $this->pdo->prepare($sql2);
$stmt2->execute([':dest' => $destinationId, ':id' => $docId]);
// 3. Journaliser le transfert
$sql3 = "INSERT INTO logs_transfert (document_id, source, destination, heure)
VALUES (:doc_id, :source, :dest, NOW())";
$stmt3 = $this->pdo->prepare($sql3);
$stmt3->execute([
':doc_id' => $docId,
':source' => $sourceId,
':dest' => $destinationId
]);
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
error_log("Échec transfert : " . $e->getMessage());
return false;
}
}
}
// Utilisation
$transfert = new TransfertHandler($pdo);
$succes = $transfert->transfererDocument(47, 'coffre_A', 'coffre_B');
echo $succes ? "▪ Transfert réussi.\n" : "▪ Échec du transfert.\n";
?>
Gestion des Erreurs - Journaliser les Incidents
<?php
// erreurs.php
require_once 'connexion.php';
class DatabaseLogger {
private $pdo;
private $logFile = 'logs/bdd_erreurs.log';
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function executerRequeteSecurisee($sql, $params = []) {
try {
$stmt = $this->pdo->prepare($sql);
if ($stmt->execute($params)) {
// Détecter si c'est un SELECT
if (stripos(trim($sql), 'SELECT') === 0) {
return [
'success' => true,
'data' => $stmt->fetchAll(),
'count' => $stmt->rowCount()
];
}
return [
'success' => true,
'affected' => $stmt->rowCount(),
'lastId' => $this->pdo->lastInsertId()
];
}
} catch (PDOException $e) {
$this->loggerErreur($e, $sql, $params);
return [
'success' => false,
'error' => 'Erreur base de données',
'code' => $e->getCode()
];
}
return ['success' => false, 'error' => 'Échec inconnu'];
}
private function loggerErreur($exception, $sql, $params) {
$message = sprintf(
"[%s] ERREUR PDO: %s\nRequête: %s\nParams: %s\n",
date('Y-m-d H:i:s'),
$exception->getMessage(),
$sql,
json_encode($params)
);
error_log($message, 3, $this->logFile);
// Codes d'erreur critiques
$critiques = [1045, 2002, 2003, 2006];
if (in_array($exception->getCode(), $critiques)) {
$this->alerterAdmin($message);
}
}
private function alerterAdmin($message) {
// En vrai: email, SMS, notification
// Ici: log supplémentaire
error_log("ALERTE CRITIQUE: " . $message, 3, $this->logFile . '.alert');
}
}
// Utilisation
$logger = new DatabaseLogger($pdo);
// Test avec une requête valide
$resultat = $logger->executerRequeteSecurisee(
"SELECT nom, code FROM agents WHERE niveau_acces > :niveau",
[':niveau' => 5]
);
if ($resultat['success']) {
echo "▪ " . count($resultat['data']) . " agents trouvés.\n";
} else {
echo "▪ Erreur: " . $resultat['error'] . "\n";
}
?>
Requêtes Préparées - Le Bouclier
<?php
// requetes_preparees.php
require_once 'connexion.php';
class Requeteur {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
// Recherche flexible avec plusieurs conditions
public function rechercher($critères) {
$where = [];
$params = [];
if (!empty($critères['nom'])) {
$where[] = "nom LIKE :nom";
$params[':nom'] = "%{$critères['nom']}%";
}
if (!empty($critères['categorie'])) {
$where[] = "categorie = :categorie";
$params[':categorie'] = $critères['categorie'];
}
if (isset($critères['actif'])) {
$where[] = "actif = :actif";
$params[':actif'] = (int)$critères['actif'];
}
$sql = "SELECT * FROM dossiers";
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
// Insertion multiple sécurisée
public function insererMultiple($table, $donnees) {
if (empty($donnees)) return false;
// Préparer les colonnes
$colonnes = array_keys($donnees[0]);
$placeholders = array_map(fn($col) => ":$col", $colonnes);
$sql = sprintf(
"INSERT INTO %s (%s) VALUES (%s)",
$table,
implode(', ', $colonnes),
implode(', ', $placeholders)
);
$stmt = $this->pdo->prepare($sql);
foreach ($donnees as $ligne) {
$stmt->execute($ligne);
}
return true;
}
}
// Utilisation
$requeteur = new Requeteur($pdo);
// Recherche avec critères
$resultats = $requeteur->rechercher([
'nom' => 'night',
'categorie' => 'confidentiel',
'actif' => true
]);
foreach ($resultats as $r) {
echo "{$r['nom']} - {$r['date_creation']}\n";
}
?>
J'éteignis l'écran. La base de données. Un coffre-fort numérique. PDO était la clé. Les requêtes préparées, le verrou. Les transactions, la garantie que tout se passait ou rien ne se passait. Les erreurs, il fallait les attraper, les journaliser, mais jamais les montrer. Parce que dans l'ombre, une erreur exposée était une faille. Et les failles, on finissait toujours par y passer la main. Ou par y recevoir une balle.