<?php
/**
* ============================================================
* CARNET DE CONTACTS — Exercice PHP + PDO + MariaDB
* ============================================================
*
* PRÉREQUIS :
* - Apache : serveur web qui reçoit les requêtes HTTP
* - MariaDB : serveur de base de données relationnelle
* - PHP : langage côté serveur (>= 7.4), avec l'extension PDO_MySQL activée
*
* COMMENT LANCER CET EXERCICE :
* 1. Copiez ce fichier dans /var/www/html/ (ou votre DocumentRoot Apache)
* 2. Exécutez le fichier SQL schema.sql dans MariaDB pour créer la base
* 3. Ajustez les constantes DB_* ci-dessous si nécessaire
* 4. Ouvrez http://localhost/index.php dans votre navigateur
* ============================================================
*/
// ────────────────────────────────────────────────────────────
// 1. CONSTANTES DE CONNEXION À LA BASE DE DONNÉES
// ────────────────────────────────────────────────────────────
// Bonne pratique : regrouper les paramètres sensibles en haut
// du fichier (ou dans un fichier config.php séparé inclus ici).
define('DB_HOST', 'localhost'); // Adresse du serveur MariaDB
define('DB_NAME', 'carnet'); // Nom de la base de données
define('DB_USER', 'root'); // Utilisateur MariaDB
define('DB_PASS', ''); // Mot de passe (vide par défaut en local)
define('DB_CHARSET', 'utf8mb4'); // Encodage : utf8mb4 gère les emojis et accents
// ────────────────────────────────────────────────────────────
// 2. FONCTION DE CONNEXION PDO
// ────────────────────────────────────────────────────────────
/**
* PDO (PHP Data Objects) est une couche d'abstraction de base de données.
* Avantages par rapport à l'ancien mysql_* ou mysqli :
* • Compatible avec plusieurs SGBD (MySQL, PostgreSQL, SQLite…)
* • Requêtes préparées natives → protection anti-injection SQL
* • Interface orientée objet propre
*
* DSN (Data Source Name) : chaîne qui identifie le pilote et les paramètres.
* Format MySQL/MariaDB : "mysql:host=HÔTE;dbname=NOM;charset=ENCODAGE"
*/
function getConnection(): PDO
{
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
/**
* Options PDO passées au constructeur :
*
* PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
* → Lance une exception PDOException en cas d'erreur SQL.
* Sans cette option, les erreurs sont silencieuses (très dangereux !).
*
* PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
* → fetch() retourne un tableau associatif ['colonne' => 'valeur']
* au lieu d'un tableau numérique + associatif (doublon inutile).
*
* PDO::ATTR_EMULATE_PREPARES => false
* → Désactive l'émulation des requêtes préparées.
* PDO délègue la préparation au serveur MariaDB lui-même,
* ce qui est plus sûr et plus performant.
*/
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
// new PDO(dsn, utilisateur, motDePasse, options)
// Le constructeur ouvre la connexion. S'il échoue → PDOException.
return new PDO($dsn, DB_USER, DB_PASS, $options);
}
// ────────────────────────────────────────────────────────────
// 3. FONCTIONS CRUD (Create / Read / Update / Delete)
// ────────────────────────────────────────────────────────────
/**
* LIRE — Récupère tous les contacts triés alphabétiquement.
*
* $pdo->query(sql) : exécute une requête SELECT sans paramètre variable.
* fetchAll() : retourne TOUS les résultats sous forme de tableau PHP.
*/
function lireTousLesContacts(PDO $pdo): array
{
$stmt = $pdo->query("SELECT * FROM contacts ORDER BY nom, prenom");
return $stmt->fetchAll(); // Tableau de tableaux associatifs
}
/**
* LIRE UN — Récupère un seul contact par son ID.
*
* REQUÊTE PRÉPARÉE en deux étapes :
* 1. prepare() : envoie le squelette SQL au serveur. Le ":id" est un
* marqueur nommé (placeholder). MariaDB sait qu'une valeur
* sera fournie plus tard — il ne l'interprète PAS comme SQL.
* 2. execute() : envoie les vraies valeurs. Elles sont envoyées séparément
* du SQL → impossible d'injecter du code malveillant.
*
* fetch() : retourne UNE SEULE ligne (ou false si aucun résultat).
*/
function lireContact(PDO $pdo, int $id): array|false
{
$stmt = $pdo->prepare("SELECT * FROM contacts WHERE id = :id");
$stmt->execute([':id' => $id]);
return $stmt->fetch();
}
/**
* CRÉER — Insère un nouveau contact.
*
* Les marqueurs :nom, :prenom, :telephone correspondent aux clés du tableau
* passé à execute(). PDO fait la correspondance automatiquement.
*
* lastInsertId() : retourne l'ID auto-incrémenté généré par l'INSERT.
*/
function creerContact(PDO $pdo, string $nom, string $prenom, string $telephone): int
{
$sql = "INSERT INTO contacts (nom, prenom, telephone) VALUES (:nom, :prenom, :telephone)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':nom' => $nom,
':prenom' => $prenom,
':telephone' => $telephone,
]);
return (int) $pdo->lastInsertId();
}
/**
* MODIFIER — Met à jour un contact existant.
*
* rowCount() : nombre de lignes affectées par le dernier UPDATE/DELETE/INSERT.
* Ici on s'en sert pour vérifier que la mise à jour a bien eu lieu.
*/
function modifierContact(PDO $pdo, int $id, string $nom, string $prenom, string $telephone): bool
{
$sql = "UPDATE contacts SET nom = :nom, prenom = :prenom, telephone = :telephone WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':nom' => $nom,
':prenom' => $prenom,
':telephone' => $telephone,
':id' => $id,
]);
return $stmt->rowCount() > 0; // true si au moins 1 ligne modifiée
}
/**
* SUPPRIMER — Efface un contact.
*
* Même principe : requête préparée avec :id pour éviter toute injection.
*/
function supprimerContact(PDO $pdo, int $id): bool
{
$stmt = $pdo->prepare("DELETE FROM contacts WHERE id = :id");
$stmt->execute([':id' => $id]);
return $stmt->rowCount() > 0;
}
// ────────────────────────────────────────────────────────────
// 4. HELPER : NETTOYAGE DES DONNÉES UTILISATEUR
// ────────────────────────────────────────────────────────────
/**
* htmlspecialchars() convertit les caractères spéciaux HTML (<, >, ", &)
* en entités HTML (< > " &).
* Cela empêche les attaques XSS (Cross-Site Scripting) lors de l'affichage.
*
* trim() supprime les espaces en début et fin de chaîne.
*/
function nettoyer(string $valeur): string
{
return htmlspecialchars(trim($valeur), ENT_QUOTES, 'UTF-8');
}
// ────────────────────────────────────────────────────────────
// 5. TRAITEMENT DES ACTIONS (LOGIQUE CONTRÔLEUR)
// ────────────────────────────────────────────────────────────
/**
* Le pattern POST → redirect → GET (PRG) est une bonne pratique web :
* après un POST, on redirige vers GET pour éviter la re-soumission du
* formulaire si l'utilisateur rafraîchit la page.
*
* $_POST : données envoyées par un formulaire en méthode POST
* $_GET : paramètres dans l'URL (?action=modifier&id=3)
* $_SERVER['PHP_SELF'] : chemin du script courant
*/
$message = '';
$erreur = false;
$contact_edition = null; // Contact à afficher dans le formulaire d'édition
try {
// Ouverture de la connexion PDO (une seule fois pour toute la page)
$pdo = getConnection();
// Récupération de l'action demandée (défaut : "liste")
// isset() vérifie qu'une variable existe et n'est pas null
$action = isset($_GET['action']) ? $_GET['action'] : 'liste';
// ── ACTION : AJOUTER ────────────────────────────────────
if ($action === 'ajouter' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$nom = trim($_POST['nom'] ?? '');
$prenom = trim($_POST['prenom'] ?? '');
$telephone = trim($_POST['telephone'] ?? '');
if ($nom && $prenom && $telephone) {
$nouvelId = creerContact($pdo, $nom, $prenom, $telephone);
// header() envoie un en-tête HTTP de redirection
header('Location: ' . $_SERVER['PHP_SELF'] . '?message=ajout_ok');
exit; // TOUJOURS appeler exit après header('Location:...')
} else {
$message = "Tous les champs sont obligatoires.";
$erreur = true;
}
// ── ACTION : MODIFIER (affichage du formulaire pré-rempli) ──
} elseif ($action === 'modifier' && isset($_GET['id'])) {
$id = (int) $_GET['id']; // cast int = protection basique contre les valeurs non numériques
$contact_edition = lireContact($pdo, $id);
if (!$contact_edition) {
$message = "Contact introuvable.";
$erreur = true;
}
// ── ACTION : ENREGISTRER LA MODIFICATION ────────────────
} elseif ($action === 'enregistrer' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int) ($_POST['id'] ?? 0);
$nom = trim($_POST['nom'] ?? '');
$prenom = trim($_POST['prenom'] ?? '');
$telephone = trim($_POST['telephone'] ?? '');
if ($id && $nom && $prenom && $telephone) {
modifierContact($pdo, $id, $nom, $prenom, $telephone);
header('Location: ' . $_SERVER['PHP_SELF'] . '?message=modif_ok');
exit;
} else {
$message = "Tous les champs sont obligatoires.";
$erreur = true;
}
// ── ACTION : SUPPRIMER ───────────────────────────────────
} elseif ($action === 'supprimer' && isset($_GET['id'])) {
$id = (int) $_GET['id'];
supprimerContact($pdo, $id);
header('Location: ' . $_SERVER['PHP_SELF'] . '?message=suppr_ok');
exit;
}
// Messages de confirmation après redirection
if (isset($_GET['message'])) {
$messages = [
'ajout_ok' => '✅ Contact ajouté avec succès !',
'modif_ok' => '✅ Contact modifié avec succès !',
'suppr_ok' => '🗑️ Contact supprimé.',
];
$message = $messages[$_GET['message']] ?? '';
}
// Chargement de tous les contacts pour l'affichage
$contacts = lireTousLesContacts($pdo);
} catch (PDOException $e) {
/**
* PDOException est levée quand :
* - La connexion échoue (mauvais host, mot de passe, base inexistante)
* - Une requête SQL est invalide
* - Une contrainte de base de données est violée
*
* En production, NE PAS afficher $e->getMessage() directement
* (fuite d'informations sensibles). Logger dans un fichier à la place.
*/
$message = "Erreur de base de données : " . $e->getMessage();
$erreur = true;
$contacts = [];
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Carnet de Contacts</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;800&family=DM+Mono:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet">
<style>
/* ══════════════════════════════════════════════════════
VARIABLES & RESET
══════════════════════════════════════════════════════ */
:root {
--bg: #0e0f13;
--surface: #16181f;
--border: #2a2d38;
--accent: #5bffd8;
--accent2: #ff6b6b;
--text: #e8eaf2;
--muted: #6b7080;
--radius: 10px;
--font-head: 'Syne', sans-serif;
--font-mono: 'DM Mono', monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-mono);
font-size: 14px;
min-height: 100vh;
padding: 2rem 1rem 4rem;
}
/* ══════════════════════════════════════════════════════
LAYOUT
══════════════════════════════════════════════════════ */
.wrapper { max-width: 900px; margin: 0 auto; }
header {
display: flex;
align-items: baseline;
gap: 1rem;
margin-bottom: 2.5rem;
border-bottom: 1px solid var(--border);
padding-bottom: 1.2rem;
}
header h1 {
font-family: var(--font-head);
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 800;
letter-spacing: -1px;
color: var(--accent);
}
header .subtitle {
color: var(--muted);
font-size: 12px;
}
/* ══════════════════════════════════════════════════════
ALERTE / MESSAGE
══════════════════════════════════════════════════════ */
.alert {
padding: .75rem 1.2rem;
border-radius: var(--radius);
margin-bottom: 1.5rem;
font-size: 13px;
border-left: 3px solid;
}
.alert-success { background: #0e2720; border-color: var(--accent); color: var(--accent); }
.alert-danger { background: #2b1515; border-color: var(--accent2); color: var(--accent2); }
/* ══════════════════════════════════════════════════════
FORMULAIRE
══════════════════════════════════════════════════════ */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem;
margin-bottom: 2rem;
}
.card h2 {
font-family: var(--font-head);
font-weight: 600;
font-size: 1rem;
color: var(--accent);
margin-bottom: 1.2rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
align-items: end;
}
.field { display: flex; flex-direction: column; gap: .4rem; }
label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
input[type="text"], input[type="tel"] {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
font-family: var(--font-mono);
font-size: 14px;
padding: .55rem .8rem;
transition: border-color .2s;
width: 100%;
}
input:focus { outline: none; border-color: var(--accent); }
/* ══════════════════════════════════════════════════════
BOUTONS
══════════════════════════════════════════════════════ */
.btn {
display: inline-flex;
align-items: center;
gap: .4rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-family: var(--font-mono);
font-size: 13px;
padding: .55rem 1.1rem;
text-decoration: none;
transition: opacity .15s, transform .1s;
font-weight: 500;
}
.btn:hover { opacity: .85; transform: translateY(-1px); }
.btn-primary { background: var(--accent); color: #0e0f13; }
.btn-warning { background: #f0c040; color: #0e0f13; }
.btn-danger { background: var(--accent2); color: #fff; }
.btn-ghost {
background: transparent;
border: 1px solid var(--border);
color: var(--muted);
}
/* ══════════════════════════════════════════════════════
TABLE DES CONTACTS
══════════════════════════════════════════════════════ */
.table-wrap { overflow-x: auto; }
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
padding: .6rem .9rem;
font-size: 11px;
letter-spacing: .8px;
text-transform: uppercase;
color: var(--muted);
border-bottom: 1px solid var(--border);
}
td {
padding: .75rem .9rem;
border-bottom: 1px solid var(--border);
vertical-align: middle;
}
tr:last-child td { border-bottom: none; }
tr:hover td { background: rgba(91,255,216,.03); }
.td-id { color: var(--muted); font-size: 12px; }
.td-actions { display: flex; gap: .5rem; }
/* ══════════════════════════════════════════════════════
SECTION CODE (blocs pédagogiques)
══════════════════════════════════════════════════════ */
.doc-section {
margin-top: 3rem;
}
.doc-section h2 {
font-family: var(--font-head);
font-size: 1.1rem;
font-weight: 800;
color: var(--accent);
margin-bottom: .4rem;
}
.doc-section .intro {
color: var(--muted);
margin-bottom: 1rem;
line-height: 1.7;
}
.code-block {
background: #111318;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.2rem 1.4rem;
overflow-x: auto;
position: relative;
margin-bottom: 1.5rem;
}
.code-block .label-lang {
position: absolute;
top: .6rem; right: .8rem;
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 1px;
}
pre { font-family: var(--font-mono); font-size: 13px; line-height: 1.75; }
.kw { color: #c792ea; } /* mot-clé PHP */
.fn { color: #82aaff; } /* fonction/méthode */
.str { color: #c3e88d; } /* chaîne */
.cmt { color: #546e7a; font-style: italic; } /* commentaire */
.var { color: #f78c6c; } /* variable */
.acc { color: var(--accent); } /* accent */
/* vide */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--muted);
}
.empty-state span { font-size: 2.5rem; display: block; margin-bottom: .5rem; }
@media(max-width:600px) {
.form-grid { grid-template-columns: 1fr; }
.td-actions { flex-direction: column; }
}
</style>
</head>
<body>
<div class="wrapper">
<!-- ═══════════════════════════════════════════
EN-TÊTE
═══════════════════════════════════════════ -->
<header>
<h1>Carnet de Contacts</h1>
<span class="subtitle">PHP · PDO · MariaDB · Apache</span>
</header>
<!-- ═══════════════════════════════════════════
MESSAGE DE RETOUR
═══════════════════════════════════════════ -->
<?php if ($message): ?>
<div class="alert <?= $erreur ? 'alert-danger' : 'alert-success' ?>">
<?= nettoyer($message) ?>
</div>
<?php endif; ?>
<!-- ═══════════════════════════════════════════
FORMULAIRE AJOUT / ÉDITION
═══════════════════════════════════════════ -->
<?php if ($contact_edition): ?>
<!-- Formulaire pré-rempli pour la modification -->
<div class="card">
<h2>✏️ Modifier le contact</h2>
<!-- action="?action=enregistrer" → PHP recevra $_GET['action'] = 'enregistrer' -->
<form method="POST" action="?action=enregistrer">
<!-- Champ caché pour transmettre l'ID sans l'afficher -->
<input type="hidden" name="id" value="<?= (int)$contact_edition['id'] ?>">
<div class="form-grid">
<div class="field">
<label for="nom">Nom</label>
<input type="text" id="nom" name="nom"
value="<?= nettoyer($contact_edition['nom']) ?>" required>
</div>
<div class="field">
<label for="prenom">Prénom</label>
<input type="text" id="prenom" name="prenom"
value="<?= nettoyer($contact_edition['prenom']) ?>" required>
</div>
<div class="field">
<label for="telephone">Téléphone</label>
<input type="tel" id="telephone" name="telephone"
value="<?= nettoyer($contact_edition['telephone']) ?>" required>
</div>
<div class="field">
<button type="submit" class="btn btn-primary">💾 Enregistrer</button>
</div>
</div>
</form>
<br>
<a href="<?= $_SERVER['PHP_SELF'] ?>" class="btn btn-ghost">← Annuler</a>
</div>
<?php else: ?>
<!-- Formulaire d'ajout -->
<div class="card">
<h2>➕ Ajouter un contact</h2>
<form method="POST" action="?action=ajouter">
<div class="form-grid">
<div class="field">
<label for="nom">Nom</label>
<input type="text" id="nom" name="nom" placeholder="Dupont" required>
</div>
<div class="field">
<label for="prenom">Prénom</label>
<input type="text" id="prenom" name="prenom" placeholder="Marie" required>
</div>
<div class="field">
<label for="telephone">Téléphone</label>
<input type="tel" id="telephone" name="telephone" placeholder="06 12 34 56 78" required>
</div>
<div class="field">
<button type="submit" class="btn btn-primary">+ Ajouter</button>
</div>
</div>
</form>
</div>
<?php endif; ?>
<!-- ═══════════════════════════════════════════
LISTE DES CONTACTS
═══════════════════════════════════════════ -->
<div class="card">
<h2>📋 Liste des contacts (<?= count($contacts) ?>)</h2>
<div class="table-wrap">
<?php if (empty($contacts)): ?>
<div class="empty-state">
<span>📭</span>
Aucun contact pour l'instant. Ajoutez-en un ci-dessus !
</div>
<?php else: ?>
<table>
<thead>
<tr>
<th>#</th>
<th>Nom</th>
<th>Prénom</th>
<th>Téléphone</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
/**
* foreach ($contacts as $c) :
* $contacts est le tableau retourné par fetchAll().
* Chaque $c est un tableau associatif :
* ['id' => 1, 'nom' => 'Dupont', 'prenom' => 'Marie', 'telephone' => '06...']
*/
foreach ($contacts as $c): ?>
<tr>
<td class="td-id"><?= (int)$c['id'] ?></td>
<td><?= nettoyer($c['nom']) ?></td>
<td><?= nettoyer($c['prenom']) ?></td>
<td><?= nettoyer($c['telephone']) ?></td>
<td class="td-actions">
<!-- Lien vers le formulaire d'édition -->
<a href="?action=modifier&id=<?= (int)$c['id'] ?>"
class="btn btn-warning">✏️ Éditer</a>
<!-- Lien de suppression avec confirmation JS -->
<a href="?action=supprimer&id=<?= (int)$c['id'] ?>"
class="btn btn-danger"
onclick="return confirm('Supprimer ce contact ?')">🗑️</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<!-- ═══════════════════════════════════════════
DOCUMENTATION PÉDAGOGIQUE
═══════════════════════════════════════════ -->
<div class="doc-section">
<h2>📖 Guide pédagogique — PDO & PHP</h2>
<p class="intro">
Les blocs ci-dessous expliquent les commandes clés utilisées dans ce script.
Lisez-les en parallèle du code source.
</p>
<!-- ── 1. DSN & Connexion ── -->
<div class="code-block">
<span class="label-lang">PHP · Connexion</span>
<pre>
<span class="cmt">// 1. DSN — Data Source Name</span>
<span class="var">$dsn</span> = <span class="str">"mysql:host=localhost;dbname=carnet;charset=utf8mb4"</span>;
<span class="cmt">// 2. new PDO(dsn, user, pass, options)</span>
<span class="cmt">// Ouvre la connexion. Lance PDOException si échec.</span>
<span class="var">$pdo</span> = <span class="kw">new</span> <span class="acc">PDO</span>(<span class="var">$dsn</span>, <span class="str">'root'</span>, <span class="str">''</span>, [
<span class="acc">PDO</span>::ATTR_ERRMODE => <span class="acc">PDO</span>::ERRMODE_EXCEPTION,
<span class="acc">PDO</span>::ATTR_DEFAULT_FETCH_MODE => <span class="acc">PDO</span>::FETCH_ASSOC,
<span class="acc">PDO</span>::ATTR_EMULATE_PREPARES => <span class="kw">false</span>,
]);</pre>
</div>
<!-- ── 2. Requête préparée INSERT ── -->
<div class="code-block">
<span class="label-lang">PHP · INSERT préparé</span>
<pre>
<span class="cmt">// prepare() envoie le squelette SQL — les :placeholders ne sont PAS du SQL</span>
<span class="var">$stmt</span> = <span class="var">$pdo</span>-><span class="fn">prepare</span>(<span class="str">"INSERT INTO contacts (nom, prenom, telephone)
VALUES (:nom, :prenom, :telephone)"</span>);
<span class="cmt">// execute() lie les valeurs réelles aux placeholders (hors-bande → anti-injection)</span>
<span class="var">$stmt</span>-><span class="fn">execute</span>([
<span class="str">':nom'</span> => <span class="var">$nom</span>,
<span class="str">':prenom'</span> => <span class="var">$prenom</span>,
<span class="str">':telephone'</span> => <span class="var">$telephone</span>,
]);
<span class="cmt">// ID auto-incrémenté de la ligne créée</span>
<span class="var">$newId</span> = <span class="var">$pdo</span>-><span class="fn">lastInsertId</span>();</pre>
</div>
<!-- ── 3. SELECT + fetchAll ── -->
<div class="code-block">
<span class="label-lang">PHP · SELECT / fetchAll</span>
<pre>
<span class="cmt">// query() = raccourci pour les SELECT sans paramètre variable</span>
<span class="var">$stmt</span> = <span class="var">$pdo</span>-><span class="fn">query</span>(<span class="str">"SELECT * FROM contacts ORDER BY nom"</span>);
<span class="cmt">// fetchAll() : retourne TOUS les résultats en un tableau PHP</span>
<span class="var">$contacts</span> = <span class="var">$stmt</span>-><span class="fn">fetchAll</span>();
<span class="cmt">// → [ ['id'=>1,'nom'=>'Dupont','prenom'=>'Marie','telephone'=>'06...'], ... ]</span>
<span class="cmt">// fetch() : retourne UNE SEULE ligne (ou false)</span>
<span class="var">$un</span> = <span class="var">$stmt</span>-><span class="fn">fetch</span>();</pre>
</div>
<!-- ── 4. UPDATE ── -->
<div class="code-block">
<span class="label-lang">PHP · UPDATE</span>
<pre>
<span class="var">$stmt</span> = <span class="var">$pdo</span>-><span class="fn">prepare</span>(<span class="str">"UPDATE contacts
SET nom=:nom, prenom=:prenom, telephone=:telephone
WHERE id=:id"</span>);
<span class="var">$stmt</span>-><span class="fn">execute</span>([<span class="str">':nom'</span>=><span class="var">$nom</span>, <span class="str">':prenom'</span>=><span class="var">$prenom</span>,
<span class="str">':telephone'</span>=><span class="var">$tel</span>, <span class="str">':id'</span>=><span class="var">$id</span>]);
<span class="cmt">// rowCount() : nb de lignes affectées par le dernier DML (INSERT/UPDATE/DELETE)</span>
<span class="kw">if</span> (<span class="var">$stmt</span>-><span class="fn">rowCount</span>() > 0) {
<span class="kw">echo</span> <span class="str">"Mise à jour réussie"</span>;
}</pre>
</div>
<!-- ── 5. DELETE ── -->
<div class="code-block">
<span class="label-lang">PHP · DELETE</span>
<pre>
<span class="var">$stmt</span> = <span class="var">$pdo</span>-><span class="fn">prepare</span>(<span class="str">"DELETE FROM contacts WHERE id = :id"</span>);
<span class="var">$stmt</span>-><span class="fn">execute</span>([<span class="str">':id'</span> => <span class="var">$id</span>]);
<span class="cmt">// Sans requête préparée : "DELETE FROM contacts WHERE id = " . $id
// → RISQUE d'injection si $id vient de l'utilisateur !</span></pre>
</div>
<!-- ── 6. Gestion d'erreur ── -->
<div class="code-block">
<span class="label-lang">PHP · Gestion d'erreurs</span>
<pre>
<span class="kw">try</span> {
<span class="var">$pdo</span> = <span class="kw">new</span> <span class="acc">PDO</span>(<span class="var">$dsn</span>, <span class="var">$user</span>, <span class="var">$pass</span>, <span class="var">$options</span>);
<span class="cmt">// ... opérations ...</span>
} <span class="kw">catch</span> (<span class="acc">PDOException</span> <span class="var">$e</span>) {
<span class="cmt">// En DEV : afficher l'erreur pour déboguer</span>
<span class="kw">echo</span> <span class="str">"Erreur : "</span> . <span class="var">$e</span>-><span class="fn">getMessage</span>();
<span class="cmt">// En PROD : logger dans un fichier, afficher un message générique</span>
<span class="fn">error_log</span>(<span class="var">$e</span>-><span class="fn">getMessage</span>());
}</pre>
</div>
<!-- ── 7. XSS ── -->
<div class="code-block">
<span class="label-lang">PHP · Sécurité XSS</span>
<pre>
<span class="cmt">// NE JAMAIS afficher une donnée utilisateur sans l'échapper :</span>
<span class="kw">echo</span> <span class="var">$_POST</span>[<span class="str">'nom'</span>]; <span class="cmt">// ❌ Dangereux : script malveillant possible</span>
<span class="cmt">// TOUJOURS utiliser htmlspecialchars() à l'affichage :</span>
<span class="kw">echo</span> <span class="fn">htmlspecialchars</span>(<span class="var">$_POST</span>[<span class="str">'nom'</span>], ENT_QUOTES, <span class="str">'UTF-8'</span>); <span class="cmt">// ✅</span>
<span class="cmt">// Transforme < > " ' & en entités HTML inoffensives.</span></pre>
</div>
</div><!-- /doc-section -->
</div><!-- /wrapper -->
</body>
</html>