Formulaires PHP 📋

GET, POST & Sécurité

Formation DWWM — Interagir avec l'utilisateur

2025 – 2026

Au programme aujourd'hui

  • 🔗 Méthode GET : URL, paramètres, anatomie d'une URL
  • 📋 Formulaires GET : recherche, filtres, conservation des valeurs
  • 📨 Formulaires POST : envoi sécurisé, validation complète, pattern PRG
  • 🛡️ Sécurité : XSS, htmlspecialchars, fonctions de validation
  • ⚖️ GET vs POST : quand utiliser quoi
  • 🔍 Console réseau (DevTools)
  • 💪 Exercices pratiques
🔗

Partie 1

La méthode GET via URL

Passer des données dans l'URL

Qu'est-ce que GET ? 🤔

GET est une méthode HTTP qui transmet des données directement dans l'URL, après un point d'interrogation ?. Ces paramètres sont visibles dans la barre d'adresse, conservés dans l'historique, et bookmarkables. Le serveur les reçoit dans le tableau $_GET.

📬 Analogie postale

  • 📮 Adresse de base = page.php
  • 📦 Informations ajoutées = les paramètres
  • 🏷️ Format : ?clé=valeur&clé2=val2
  • 👁️ Tout est écrit sur l'enveloppe — visible de tous

📡 Requête HTTP réelle

GET /produit.php?id=5&couleur=rouge HTTP/1.1
Host: monsite.com
User-Agent: Mozilla/5.0...

(pas de corps de requête — tout est dans l'URL)

Anatomie d'une URL avec GET 🔍

http://monsite.com/page.php?nom=Jean&age=25&ville=Paris
ÉlémentRôleDans l'exemple
URL de baseAdresse de la page PHPhttp://monsite.com/page.php
?Début des paramètres — une seule foisSéparateur obligatoire
cléNom du paramètre → clé dans $_GETnom, age, ville
=Sépare la clé de sa valeur—
valeurContenu du paramètreJean, 25, Paris
&Sépare les paramètres entre euxEntre chaque paire clé=valeur
💡 Dans le HTML, le & entre paramètres s'écrit & pour être valide. En PHP pur, utilisez & directement.

Créer des liens avec paramètres GET 🔗

<!-- Lien simple -->
<a href="page.php">Cliquez ici</a>

<!-- Lien avec un paramètre -->
<a href="page.php?nom=Marie">Voir le profil de Marie</a>

<!-- Lien avec plusieurs paramètres -->
<a href="produit.php?id=10&couleur=bleu&taille=M">Voir le produit</a>

<?php
$userId = 42;
$page   = 2;
?>

<!-- ❌ Valeurs codées en dur : rigide, peu maintenable -->
<a href="profil.php?id=42&page=2">Mon profil</a>

<!-- ✅ Variables PHP injectées dans l'URL -->
<a href="profil.php?id=<?= $userId ?>&page=<?= $page ?>">Mon profil</a>

<!-- ✅ Encore mieux : http_build_query() encode automatiquement
     les espaces et caractères spéciaux (accents, &, etc.) -->
<?php $params = http_build_query(['id' => $userId, 'nom' => 'Marie Dupont']); ?>
<a href="profil.php?<?= $params ?>">Mon profil</a>
<!-- Génère : profil.php?id=42&nom=Marie+Dupont -->

Récupérer les paramètres avec $_GET 📬

$_GET est un tableau associatif (clé → valeur) rempli automatiquement par PHP avec tous les paramètres de l'URL. Attention : toutes les valeurs sont des strings, même les nombres.
<?php
// URL reçue : page.php?nom=Marie&age=25

// ❌ Dangereux : PHP génère un Warning si la clé n'existe pas
$nom = $_GET['nom'];

// ✅ Avec isset() — vérifier l'existence avant de lire
if (isset($_GET['nom'])) {
    $nom = $_GET['nom'];
    echo "Bonjour $nom";
} else {
    echo "Nom non fourni";
}

// ✅ Avec ?? (coalescence null) — plus concis, valeur par défaut
$nom  = $_GET['nom']  ?? 'Visiteur'; // 'Visiteur' si la clé est absente
$page = $_GET['page'] ?? 1;

// ⚠️ Les valeurs de $_GET sont TOUJOURS des strings !
$age = $_GET['age'];        // "25" (string), pas 25 (int)
$age = (int)$_GET['age'];   // ✅ Convertir explicitement si nécessaire

// Debug : inspecter tout le tableau
var_dump($_GET);
?>
⚠️ $_GET['age'] retourne toujours une string, même si l'URL contient ?age=25. Castez toujours avec (int), (float) selon le type attendu.

isset() vs ?? — Quelle différence ? 🤔

<?php
// isset() → true si la clé existe ET n'est pas null
if (isset($_GET['nom'])) {
    $nom = $_GET['nom'];
    echo "Bonjour $nom";
}

// ?? (coalescence null) → valeur si elle existe, sinon la valeur par défaut
// C'est un raccourci pour : isset($_GET['nom']) ? $_GET['nom'] : 'Visiteur'
$nom = $_GET['nom'] ?? 'Visiteur';

// Cas pratiques
$id   = (int)($_GET['id']      ?? 0);      // Entier, 0 par défaut
$page = max(1, (int)($_GET['page'] ?? 1)); // Au minimum 1, entier forcé
$tri  = $_GET['tri'] ?? 'nom';             // Valeur métier par défaut

// Vérifier la simple présence (true/false) sans lire la valeur
$modeDebug = isset($_GET['debug']); // true si ?debug est dans l'URL

// Validation après récupération
$id = (int)($_GET['id'] ?? 0);
if ($id <= 0) {
    die("Erreur : ID invalide");
}
?>
💡 Préférez ?? pour le code simple avec une valeur par défaut. Utilisez isset() quand vous devez exécuter un bloc de code différent selon la présence ou l'absence de la clé.

Exemple complet : Page de profil 👤

<?php // profil.php

// 1. Récupérer et valider l'ID
$userId = $_GET['id'] ?? null;

if ($userId === null) {
    die("Erreur : Aucun utilisateur spécifié");
}

// ctype_digit() est plus strict que is_numeric() :
// is_numeric("3.5") → true  |  ctype_digit("3.5") → false  ✅
// is_numeric("-1")  → true  |  ctype_digit("-1")  → false  ✅
if (!ctype_digit((string)$userId)) {
    die("Erreur : L'ID doit être un entier positif");
}
$userId = (int)$userId;

// 2. Simuler une BDD
$utilisateurs = [
    1 => ["nom" => "Dupont", "prenom" => "Marie", "role" => "admin"],
    2 => ["nom" => "Martin", "prenom" => "Jean",  "role" => "user"],
];

if (!isset($utilisateurs[$userId])) {
    die("Erreur : Utilisateur introuvable");
}

$user = $utilisateurs[$userId];
?>
<h1>Profil : <?= htmlspecialchars($user['prenom']) ?> <?= htmlspecialchars($user['nom']) ?></h1>
<p>Rôle : <?= htmlspecialchars($user['role']) ?></p>
💡 ctype_digit() vérifie que la chaîne ne contient que des chiffres (0–9), sans signe ni virgule — idéal pour valider des IDs de base de données.

Cas d'usage de GET 🎯

✅ Utilisez GET pour :

  • 📄 Pages de détail : produit.php?id=5
  • 📑 Pagination : liste.php?page=2
  • 🔍 Filtres : catalogue.php?cat=tech
  • 📊 Tri : resultats.php?order=prix&dir=asc
  • 🔗 Liens partageables : article.php?id=123
  • 📖 Lecture seule de données

❌ N'utilisez PAS GET pour :

  • 🔐 Mots de passe (visibles dans l'URL !)
  • 💳 Données sensibles (carte bancaire…)
  • 📝 Formulaires longs (> ~2 000 caractères)
  • 🛒 Validation de commande
  • ✏️ Création ou modification de données
  • 🗑️ Suppression de données
Règle : GET = lire des données. L'URL peut être copiée, partagée, mise en favori — son contenu doit pouvoir être vu sans risque.
📋

Partie 2

Formulaires avec GET

Recherche, filtres, conservation des valeurs

Structure d'un formulaire GET 📝

<form method="GET"> : À la soumission, chaque champ portant un attribut name devient un paramètre dans l'URL. C'est exactement comme si l'utilisateur avait tapé l'URL avec ses paramètres à la main.
<form method="GET" action="recherche.php">
    <label for="q">Rechercher :</label>
    <input type="text" id="q" name="q" placeholder="Ex: smartphone">

    <button type="submit">Rechercher</button>
</form>

<!--
  L'utilisateur tape "smartphone" et clique le bouton :
  → URL générée  : recherche.php?q=smartphone
  → PHP reçoit   : $_GET['q'] === "smartphone"

  Avec deux champs (name="q" et name="categorie") :
  → URL générée  : recherche.php?q=smartphone&categorie=tech
  → PHP reçoit   : $_GET['q'] === "smartphone"
                   $_GET['categorie'] === "tech"
-->
💡 L'attribut name définit la clé dans $_GET. Un champ sans name n'est jamais envoyé. L'attribut id ne sert qu'à relier le <label for=""> pour l'accessibilité.

Attributs importants du formulaire 🏷️

AttributOù ?RôleExemple
method<form>Méthode HTTP d'envoiGET ou POST
action<form>Page de destination (fichier courant si omis)recherche.php
nameChampClé dans $_GET ou $_POSTname="email"
idChampLiaison avec <label for="">id="email"
valueChampValeur pré-remplie (conservation)value="test"
requiredChampChamp obligatoire côté HTMLrequired
placeholderChampTexte indicatif griséplaceholder="Ex: Jean"
⚠️ required est une validation HTML contournable via les DevTools. Validez toujours côté serveur en PHP — la validation HTML n'est qu'une aide à l'UX.

Exemple : Formulaire de recherche 🔍

<?php // recherche.php
$recherche = trim($_GET['q'] ?? '');

// Simuler une BDD — en production : requête SQL avec LIKE '%terme%'
$produits  = ["Ordinateur", "Souris", "Clavier", "Écran", "Webcam"];
$resultats = [];

if ($recherche !== '') {
    foreach ($produits as $p) {
        // stripos : cherche $recherche dans $p, insensible à la casse
        // Retourne false si non trouvé, la position (int) si trouvé
        if (stripos($p, $recherche) !== false) {
            $resultats[] = $p;
        }
    }
}
?>
<h1>Rechercher un produit</h1>

<form method="GET">
    <!-- value= conserve le terme saisi après soumission -->
    <input type="text" name="q"
           value="<?= htmlspecialchars($recherche) ?>"
           placeholder="Ex: souris">
    <button type="submit">Rechercher</button>
    <?php if ($recherche): ?>
        <a href="?">✕ Effacer</a>
    <?php endif; ?>
</form>

<?php if ($recherche !== ''): ?>
    <h2>Résultats pour "<?= htmlspecialchars($recherche) ?>" :</h2>
    <?php if ($resultats): ?>
        <ul><?php foreach ($resultats as $r): ?>
            <li><?= htmlspecialchars($r) ?></li>
        <?php endforeach; ?></ul>
    <?php else: ?>
        <p>Aucun résultat trouvé.</p>
    <?php endif; ?>
<?php endif; ?>

Formulaire avec plusieurs champs 📋

<?php // filtre.php
$categorie = $_GET['categorie'] ?? 'tous';
$prixMin   = (int)($_GET['prix_min'] ?? 0);
$prixMax   = (int)($_GET['prix_max'] ?? 1000);
?>

<form method="GET">
    <label>Catégorie :
        <select name="categorie">
            <option value="tous"         <?= $categorie === 'tous'         ? 'selected' : '' ?>>Tous</option>
            <option value="electronique" <?= $categorie === 'electronique' ? 'selected' : '' ?>>Électronique</option>
            <option value="vetements"    <?= $categorie === 'vetements'    ? 'selected' : '' ?>>Vêtements</option>
        </select>
    </label>

    <label>Prix min :
        <input type="number" name="prix_min" value="<?= $prixMin ?>" min="0">
    </label>

    <label>Prix max :
        <input type="number" name="prix_max" value="<?= $prixMax ?>" min="0">
    </label>

    <button type="submit">Filtrer</button>
</form>

<!--
  URL après soumission :
  filtre.php?categorie=electronique&prix_min=100&prix_max=500
-->
💡 Réafficher les valeurs sélectionnées est essentiel pour l'UX : l'utilisateur voit ses filtres conservés après avoir cliqué "Filtrer".

Réafficher checkbox, radio et select ✅

<?php
$categorie = $_GET['categorie'] ?? 'tous';
$promo     = isset($_GET['promo']); // true si la case était cochée
$tri       = $_GET['tri'] ?? 'nom';
?>

<!-- SELECT → attribut selected sur la bonne option -->
<select name="categorie">
    <option value="tous" <?= $categorie === 'tous' ? 'selected' : '' ?>>Tous</option>
    <option value="tech" <?= $categorie === 'tech' ? 'selected' : '' ?>>Tech</option>
</select>

<!-- CHECKBOX → attribut checked si la clé était présente dans l'URL -->
<input type="checkbox" name="promo"
       <?= $promo ? 'checked' : '' ?>> En promotion seulement

<!-- RADIO → checked sur le bouton correspondant à la valeur reçue -->
<input type="radio" name="tri" value="nom"  <?= $tri === 'nom'  ? 'checked' : '' ?>> Par nom
<input type="radio" name="tri" value="prix" <?= $tri === 'prix' ? 'checked' : '' ?>> Par prix

<!-- INPUT text/number → attribut value -->
<input type="text"   name="q"        value="<?= htmlspecialchars($_GET['q'] ?? '') ?>">
<input type="number" name="prix_max" value="<?= (int)($_GET['prix_max'] ?? 1000) ?>">
Résumé : <input> / <textarea> → value="" · <option> → selected · checkbox / radio → checked
📨

Partie 3

Formulaires avec POST

Envoyer des données de manière sécurisée

Qu'est-ce que POST ? 📮

POST est une méthode HTTP qui envoie les données dans le corps de la requête, invisible dans l'URL. Le serveur les reçoit dans $_POST. Utilisé pour tout ce qui modifie des données ou contient des informations sensibles.

📡 Requête HTTP POST réelle

POST /contact.php HTTP/1.1
Host: monsite.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 42

nom=Dupont&email=a@b.com&message=Bonjour

↑ Données dans le corps — URL reste propre

Différences clés vs GET

  • 🔒 Données invisibles dans l'URL
  • 📦 Taille pratiquement illimitée
  • 📎 Seule méthode pour uploader des fichiers
  • 🚫 URL non partageable / non bookmarkable
  • ⚠️ F5 propose de re-soumettre les données !

Structure d'un formulaire POST 📝

<!-- Fichier : contact.php -->
<form method="POST" action="contact.php">

    <label for="nom">Nom :</label>
    <input type="text" id="nom" name="nom" required>

    <label for="email">Email :</label>
    <input type="email" id="email" name="email" required>

    <label for="message">Message :</label>
    <textarea id="message" name="message" rows="5" required></textarea>

    <button type="submit">Envoyer</button>
</form>

<!--
  Après soumission :
  → URL reste identique : contact.php  (aucun paramètre visible)
  → PHP reçoit dans le corps :
      $_POST['nom']     === "Dupont"
      $_POST['email']   === "dupont@mail.com"
      $_POST['message'] === "Bonjour !"
-->

Récupérer les données POST 📬

Pourquoi vérifier REQUEST_METHOD ? Quand l'utilisateur arrive sur la page, c'est un GET → $_POST est vide. Quand il soumet le formulaire, c'est un POST → $_POST contient les données. Cette vérification permet de tout gérer dans un seul fichier.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // ✅ Le formulaire vient d'être soumis

    $nom     = $_POST['nom']     ?? '';
    $email   = $_POST['email']   ?? '';
    $message = $_POST['message'] ?? '';

    // Debug en développement
    var_dump($_POST);

} else {
    // Première visite → afficher le formulaire vide
}
?>
💡 $_SERVER['REQUEST_METHOD'] contient toujours la méthode HTTP de la requête en cours : "GET", "POST", "PUT", "DELETE"… C'est la façon standard de détecter une soumission de formulaire.

Exemple complet : Formulaire de contact ✉️

<?php // contact.php
$nom = $email = $message = '';
$succes  = false;
$erreurs = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. Récupérer et nettoyer (trim supprime les espaces début/fin)
    $nom     = trim($_POST['nom']     ?? '');
    $email   = trim($_POST['email']   ?? '');
    $message = trim($_POST['message'] ?? '');

    // 2. Valider
    if (empty($nom))     $erreurs[] = "Le nom est obligatoire";
    if (empty($email))   $erreurs[] = "L'email est obligatoire";
    elseif (!filter_var($email, FILTER_VALIDATE_EMAIL))
                         $erreurs[] = "L'email n'est pas valide";
    if (empty($message)) $erreurs[] = "Le message est obligatoire";

    // 3. Traiter si tout est valide
    if (empty($erreurs)) {
        // mail($email, "Contact", $message); // ou enregistrement BDD
        $succes = true;
        $nom = $email = $message = ''; // Vider les champs
    }
}
?>
<?php if ($succes): ?>
    <p style="color:green">✅ Message envoyé avec succès !</p>
<?php endif; ?>

<?php if (!empty($erreurs)): ?>
    <ul style="color:red"><?php foreach ($erreurs as $e): ?>
        <li><?= htmlspecialchars($e) ?></li>
    <?php endforeach; ?></ul>
<?php endif; ?>

<form method="POST">
    <input name="nom"    value="<?= htmlspecialchars($nom) ?>"   placeholder="Nom">
    <input name="email"  value="<?= htmlspecialchars($email) ?>" placeholder="Email" type="email">
    <textarea name="message"><?= htmlspecialchars($message) ?></textarea>
    <button>Envoyer</button>
</form>

Validation complète des données POST ✅

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $erreurs = [];

    $email = trim($_POST['email']       ?? '');
    $age   = trim($_POST['age']         ?? '');
    $cp    = trim($_POST['code_postal'] ?? '');
    $url   = trim($_POST['site']        ?? '');
    $nom   = trim($_POST['nom']         ?? '');

    // Champ vide
    if (empty($email)) $erreurs[] = "Email requis";

    // Format email
    if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL))
        $erreurs[] = "Format email invalide";

    // Entier dans une plage (ctype_digit plus strict que is_numeric)
    if (!ctype_digit($age) || (int)$age < 18 || (int)$age > 99)
        $erreurs[] = "L'âge doit être entre 18 et 99";

    // Code postal français : exactement 5 chiffres
    if (!preg_match('/^[0-9]{5}$/', $cp))
        $erreurs[] = "Code postal invalide (5 chiffres requis)";

    // URL valide
    if ($url && !filter_var($url, FILTER_VALIDATE_URL))
        $erreurs[] = "URL invalide";

    // Longueur minimale
    if (strlen($nom) < 2)
        $erreurs[] = "Nom trop court (minimum 2 caractères)";

    if (empty($erreurs)) {
        // ✅ Données valides → traitement
    }
}
?>

Pattern PRG : éviter la double soumission 🔄

PRG = Post → Redirect → Get
Problème : si l'utilisateur appuie sur F5 après un POST, le navigateur propose de re-soumettre → risque de doublons en base. Solution : après un POST réussi, rediriger vers une page GET. Le F5 recharge alors le GET de confirmation, pas le POST.
<?php // inscription.php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nom = trim($_POST['nom'] ?? '');
    // ... validation ...
    // ... enregistrement en BDD ...

    // Stocker le message en session (flash message)
    $_SESSION['flash'] = "Bienvenue $nom, inscription réussie !";

    // ← Redirection GET (le POST est terminé)
    header('Location: confirmation.php');
    exit; // ← Toujours obligatoire après header('Location:')
}
?>

<?php // confirmation.php
session_start();
if (isset($_SESSION['flash'])) {
    echo '<p style="color:green">' . htmlspecialchars($_SESSION['flash']) . '</p>';
    unset($_SESSION['flash']); // Supprimer après affichage = "flash"
}
?>
💡 Ce pattern "flash message" est utilisé par tous les frameworks modernes (Symfony, Laravel…). Le message s'affiche une seule fois puis disparaît au rechargement.
🛡️

Partie 4

Sécurité des formulaires

XSS, échappement et bonnes pratiques

Qu'est-ce que le XSS ? 🦠

XSS = Cross-Site Scripting — Attaque où un pirate injecte du code JavaScript malveillant dans une page web. Si le site affiche des données utilisateur sans les sécuriser, le JS s'exécute dans le navigateur de chaque victime qui visite la page.

Déroulement de l'attaque

1. Pirate saisit dans un champ :
<script>document.cookie</script>
↓
2. Site affiche sans protection
↓
3. JS s'exécute chez
tous les visiteurs
↓ Conséquences :
💀 Vol de cookies / session
💀 Redirection vers site pirate
💀 Défacement de la page

3 types de XSS

  • Réfléchi : via l'URL (?msg=<script>)
    → affecte uniquement qui clique sur le lien piégé
  • Stocké ⚠️ : injecté en BDD (commentaire, pseudo)
    → affecte tous les visiteurs à chaque chargement
  • DOM-based : manipulation JS côté client uniquement

Code vulnérable vs code sécurisé 🔴🟢

<?php
// ❌ CODE VULNÉRABLE
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nom = $_POST['nom']; // Aucune protection

    // Le code JS s'exécute directement !
    echo "Bonjour $nom !";
}
?>
<form method="POST">
    <input type="text" name="nom">
    <button>Envoyer</button>
</form>

<!-- Pirate saisit :
<script>alert('Piraté!')</script>
→ La boîte JS s'exécute ! 💀 -->
<?php
// ✅ CODE SÉCURISÉ
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nom = $_POST['nom'] ?? '';

    // htmlspecialchars neutralise < > " ' &
    echo "Bonjour " . htmlspecialchars($nom) . " !";
}
?>
<form method="POST">
    <input type="text" name="nom">
    <button>Envoyer</button>
</form>

<!-- Pirate saisit le même code :
→ Affiché comme texte brut ✅
→ Aucun JS exécuté ! -->

La solution : htmlspecialchars() 🛡️

htmlspecialchars() convertit les caractères spéciaux HTML en entités texte — ils s'affichent correctement à l'écran, mais ne sont jamais interprétés comme du code par le navigateur.
Caractère dangereuxEntité produitePourquoi dangereux
<&lt;Ouvre une balise HTML ou un <script>
>&gt;Ferme une balise HTML
"&quot;Ferme un attribut en double guillemets
'&#039;Ferme un attribut en simple guillemets
&&amp;Début d'une entité HTML
<?php
// Syntaxe recommandée : ENT_QUOTES échappe aussi les guillemets simples
htmlspecialchars($texte, ENT_QUOTES, 'UTF-8');

// Créer un alias court — très pratique dans les templates PHP
function e(string $text): string {
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
echo e($nom); // Court et toujours sécurisé
?>

Où utiliser htmlspecialchars() ? 🎯

<?php function e($t) { return htmlspecialchars($t, ENT_QUOTES, 'UTF-8'); } ?>

<!-- ✅ Dans le contenu d'une balise -->
<p>Bonjour <?= e($nom) ?> !</p>
<h1><?= e($titre) ?></h1>

<!-- ✅ Dans les attributs : value, placeholder, title, alt… -->
<input type="text" name="nom" value="<?= e($nom) ?>">
<input placeholder="<?= e($hint) ?>">
<img alt="<?= e($description) ?>" src="photo.jpg">

<!-- ✅ Dans un textarea (le contenu va entre les balises, pas dans value) -->
<textarea name="message"><?= e($message) ?></textarea>

<!-- ✅ Dans le titre de la page -->
<title><?= e($titrePage) ?></title>

<!-- ❌ PAS dans les requêtes SQL → utiliser PDO avec requêtes préparées -->
<!-- ❌ PAS dans les comparaisons PHP : if ($nom === "test") -->
<!-- ❌ PAS avant d'enregistrer en BDD (stocker les données brutes) -->
Règle d'or : Sécuriser à la sortie (affichage HTML), jamais à l'entrée. Stockez les données brutes en base, appliquez htmlspecialchars() uniquement au moment d'afficher.

Autres fonctions de nettoyage et validation 🔐

<?php
// trim() — Supprimer espaces et sauts de ligne en début/fin
$nom   = trim($_POST['nom'] ?? '');    // "  Marie  " → "Marie"
$email = trim($_POST['email'] ?? '');  // " a@b.com\n" → "a@b.com"

// strip_tags() — Supprimer toutes les balises HTML
$texte = strip_tags($_POST['bio'] ?? '');
// "<b>Bonjour</b> <script>hack</script>" → "Bonjour "

// filter_var() — Valider un format spécifique
$email  = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
// → Retourne l'email si valide, false sinon

$entier = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT);
// → Retourne l'entier si valide, false sinon

$url    = filter_var($_POST['site'] ?? '', FILTER_VALIDATE_URL);
// → Retourne l'URL si valide, false sinon

// ctype_digit() — Vérifie que la chaîne ne contient QUE des chiffres 0-9
// Plus strict que is_numeric() : rejette "3.5", "-1", " 5"
if (ctype_digit($_POST['code'] ?? '')) { /* Seulement des chiffres */ }

// preg_match() — Valider un format par expression régulière
if (preg_match('/^[0-9]{5}$/', $_POST['cp'] ?? ''))
    echo "Code postal valide (exactement 5 chiffres)";

if (preg_match('/^[a-zA-ZÀ-ÿ\s\-]{2,50}$/', $_POST['nom'] ?? ''))
    echo "Nom valide (lettres, espaces, tirets, 2 à 50 caractères)";
?>

Checklist sécurité formulaire ✅

  • htmlspecialchars() à chaque affichage
  • Valider TOUTES les entrées utilisateur
  • filter_var() pour emails, URLs, entiers
  • Vérifier les types : ctype_digit()
  • Limiter la taille (maxlength + vérif PHP)
  • HTTPS pour les données sensibles
  • Token CSRF sur les formulaires POST
  • Ne jamais faire confiance à GET/POST
  • POST pour mots de passe et données perso
  • PDO + requêtes préparées pour la BDD
  • trim() avant toute validation
  • Pattern PRG pour éviter les doublons
⚖️

Partie 5

GET vs POST

Quand utiliser quoi ?

Tableau comparatif complet 📊

CritèreGETPOST
Emplacement des données👁️ Dans l'URL🔒 Corps de la requête HTTP
Taille max~2 000 caractèresIllimitée (selon config serveur)
Bookmarkable✅ Oui❌ Non
Cache navigateur✅ Oui❌ Non
Historique✅ Conservé❌ Non
Sécurité relative⚠️ Données visibles🛡️ Données cachées
Upload fichiers❌ Impossible✅ Oui (enctype="multipart/form-data")
F5 rechargement✅ Inoffensif⚠️ Propose de re-soumettre !
Idempotent✅ Même résultat à chaque appel❌ Peut créer des doublons

Quand GET ? Quand POST ? 🎯

🔗 GET pour :

  • 🔍 Recherche : recherche.php?q=laptop
  • 📑 Pagination : liste.php?page=3
  • 🎚️ Filtres : produits.php?cat=tech&max=500
  • 📊 Tri : resultats.php?order=prix&dir=asc
  • 🔗 Liens partageables : article.php?id=123
  • 📖 Lecture seule de données
💡 Règle : GET pour LIRE. L'URL peut être partagée.

📨 POST pour :

  • 🔐 Connexion (login / password)
  • 📝 Inscription (création de compte)
  • 💬 Poster un commentaire ou message
  • 🛒 Valider une commande
  • 📤 Upload de fichiers
  • ✏️ Éditer ou 🗑️ supprimer des données
💡 Règle : POST pour MODIFIER ou CRÉER.

Arbre de décision : GET ou POST ? 🌳

❓ Les données sont-elles sensibles ?
→ Oui (mot de passe, données perso…) → POST 📨
→ Non → continuer…

❓ L'action modifie-t-elle des données ?
→ Oui (créer, éditer, supprimer) → POST 📨
→ Non → continuer…

❓ L'URL doit-elle être partageable / bookmarkable ?
→ Oui (résultats de recherche, page de détail…) → GET 🔗
→ Non → continuer…

❓ Plus de 2 000 caractères de données ?
→ Oui → POST obligatoire 📨
→ Non → GET possible 🔗

Console réseau (DevTools) 🔍

DevTools → Onglet Network (Réseau) : Outil indispensable pour déboguer les formulaires. Ouvrir avec F12 ou Ctrl+Shift+I, puis soumettre le formulaire — chaque requête apparaît avec tous ses détails.

🔗 Requête GET

Request URL :
monsite.com/recherche.php?q=laptop
Request Method : GET

Query String Parameters :
q : laptop

Les paramètres sont visibles dans l'URL

📮 Requête POST

Request URL :
monsite.com/contact.php
Request Method : POST

Form Data (onglet "Payload") :
nom : Dupont
email : a@b.com

URL propre, données dans le corps
💡 Cochez "Preserve log" pour garder les requêtes après une redirection — indispensable pour déboguer le pattern PRG !
💪

Partie 6

Exercices pratiques

À vous de jouer !

Exercice 1 : Liens avec paramètres GET 🔗

📝 Énoncé : Créez produit.php qui :
• Affiche des liens vers 3 produits (?id=1, ?id=2, ?id=3)
• Récupère l'ID depuis l'URL avec $_GET
• Affiche le nom du produit selon l'ID
• Gère le cas où l'ID n'existe pas
$produits = [
    1 => "Ordinateur",
    2 => "Souris",
    3 => "Clavier"
];

Exercice 1 : Corrigé ✅

<?php
$produits = [1 => "Ordinateur", 2 => "Souris", 3 => "Clavier"];

// Valider l'ID : doit exister et ne contenir que des chiffres
$id = (isset($_GET['id']) && ctype_digit($_GET['id']))
      ? (int)$_GET['id']
      : null;
?>
<h1>Nos produits</h1>
<ul>
    <?php foreach ($produits as $pid => $nom): ?>
        <li>
            <a href="produit.php?id=<?= $pid ?>"
               <?= $id === $pid ? 'style="font-weight:bold;color:#f0ad4e"' : '' ?>>
                <?= htmlspecialchars($nom) ?>
            </a>
        </li>
    <?php endforeach; ?>
</ul>
<hr>
<?php if ($id !== null): ?>
    <?php if (isset($produits[$id])): ?>
        <h2>📦 <?= htmlspecialchars($produits[$id]) ?></h2>
        <p>ID : <?= $id ?></p>
    <?php else: ?>
        <p style="color:red">❌ Produit introuvable (ID : <?= $id ?>)</p>
    <?php endif; ?>
<?php else: ?>
    <p>Cliquez sur un produit pour le voir.</p>
<?php endif; ?>

Exercice 2 : Formulaire de recherche GET 🔍

📝 Énoncé : Créez recherche.php qui :
• Affiche un formulaire GET avec un champ texte
• Filtre une liste de fruits (insensible à la casse)
• Affiche les résultats ou "Aucun résultat"
• Conserve le terme saisi dans le champ après soumission
$fruits = ["Pomme", "Banane", "Orange", "Fraise", "Ananas", "Poire"];

Exercice 2 : Corrigé ✅

<?php
$fruits    = ["Pomme", "Banane", "Orange", "Fraise", "Ananas", "Poire"];
$recherche = trim($_GET['q'] ?? '');
$resultats = [];

if ($recherche !== '') {
    foreach ($fruits as $fruit) {
        if (stripos($fruit, $recherche) !== false) {
            $resultats[] = $fruit;
        }
    }
}
?>
<h1>🔍 Rechercher un fruit</h1>

<form method="GET">
    <input type="text" name="q"
           value="<?= htmlspecialchars($recherche) ?>"
           placeholder="Ex: Pomme">
    <button type="submit">Chercher</button>
    <?php if ($recherche): ?>
        <a href="?">✕ Effacer</a>
    <?php endif; ?>
</form>

<?php if ($recherche !== ''): ?>
    <h2>Résultats pour "<?= htmlspecialchars($recherche) ?>" :</h2>
    <?php if ($resultats): ?>
        <ul><?php foreach ($resultats as $f): ?>
            <li><?= htmlspecialchars($f) ?></li>
        <?php endforeach; ?></ul>
    <?php else: ?>
        <p>Aucun résultat trouvé.</p>
    <?php endif; ?>
<?php endif; ?>

Exercice 3 : Formulaire POST avec validation 📝

📝 Énoncé : Créez inscription.php qui :
• Formulaire POST : nom, email, age
• Valide : nom ≥ 2 cars · email valide · âge entre 18 et 99
• Affiche les erreurs en rouge
• Message de succès si tout est OK
• Conserve les valeurs en cas d'erreur

Exercice 3 : Corrigé (1/2) ✅

<?php
$nom = $email = $age = '';
$erreurs = [];
$succes  = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nom   = trim($_POST['nom']   ?? '');
    $email = trim($_POST['email'] ?? '');
    $age   = trim($_POST['age']   ?? '');

    if (empty($nom)) {
        $erreurs[] = "Le nom est obligatoire";
    } elseif (strlen($nom) < 2) {
        $erreurs[] = "Le nom doit faire au moins 2 caractères";
    }

    if (empty($email)) {
        $erreurs[] = "L'email est obligatoire";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $erreurs[] = "L'email n'est pas valide";
    }

    if (empty($age)) {
        $erreurs[] = "L'âge est obligatoire";
    } elseif (!ctype_digit($age)) {
        $erreurs[] = "L'âge doit être un nombre entier";
    } elseif ((int)$age < 18 || (int)$age > 99) {
        $erreurs[] = "L'âge doit être entre 18 et 99";
    }

    if (empty($erreurs)) {
        $succes = true;
        $nom = $email = $age = '';
    }
}
?>

Exercice 3 : Corrigé (2/2) ✅

<?php if ($succes): ?>
    <p style="color:green; font-weight:bold">✅ Inscription réussie !</p>
<?php endif; ?>

<?php if (!empty($erreurs)): ?>
    <div style="color:red; border:1px solid red; padding:10px; border-radius:6px">
        <strong>❌ Erreurs à corriger :</strong>
        <ul><?php foreach ($erreurs as $e): ?>
            <li><?= htmlspecialchars($e) ?></li>
        <?php endforeach; ?></ul>
    </div>
<?php endif; ?>

<form method="POST">
    <div>
        <label for="nom">Nom :</label>
        <input type="text" id="nom" name="nom"
               value="<?= htmlspecialchars($nom) ?>" required>
    </div>
    <div>
        <label for="email">Email :</label>
        <input type="email" id="email" name="email"
               value="<?= htmlspecialchars($email) ?>" required>
    </div>
    <div>
        <label for="age">Âge :</label>
        <input type="number" id="age" name="age"
               value="<?= htmlspecialchars($age) ?>"
               min="18" max="99" required>
    </div>
    <button type="submit">S'inscrire</button>
</form>

Exercice 4 : BONUS — Filtres multiples 🎛️

📝 Énoncé : Créez catalogue.php avec :
• Formulaire GET : catégorie (select) + prix max (number)
• 6 produits à filtrer par catégorie et prix
• Affichage du nombre de résultats
• Filtres conservés après soumission
$produits = [
    ["nom" => "Laptop",  "categorie" => "informatique", "prix" => 899],
    ["nom" => "Souris",  "categorie" => "informatique", "prix" => 25],
    ["nom" => "T-shirt", "categorie" => "vetements",    "prix" => 15],
    ["nom" => "Jean",    "categorie" => "vetements",    "prix" => 50],
    ["nom" => "Casque",  "categorie" => "informatique", "prix" => 120],
    ["nom" => "Veste",   "categorie" => "vetements",    "prix" => 89],
];

Exercice 4 : Corrigé ✅

<?php
$produits = [
    ["nom"=>"Laptop",  "categorie"=>"informatique","prix"=>899],
    ["nom"=>"Souris",  "categorie"=>"informatique","prix"=>25],
    ["nom"=>"T-shirt", "categorie"=>"vetements",   "prix"=>15],
    ["nom"=>"Jean",    "categorie"=>"vetements",   "prix"=>50],
    ["nom"=>"Casque",  "categorie"=>"informatique","prix"=>120],
    ["nom"=>"Veste",   "categorie"=>"vetements",   "prix"=>89],
];

$catFiltre = $_GET['categorie'] ?? 'tous';
$prixMax   = (int)($_GET['prix_max'] ?? 1000);

$resultats = array_filter($produits, function($p) use ($catFiltre, $prixMax) {
    if ($catFiltre !== 'tous' && $p['categorie'] !== $catFiltre) return false;
    if ($p['prix'] > $prixMax) return false;
    return true;
});
?>
<form method="GET">
    <select name="categorie">
        <option value="tous"         <?= $catFiltre==='tous'         ? 'selected':'' ?>>Tous</option>
        <option value="informatique" <?= $catFiltre==='informatique' ? 'selected':'' ?>>Informatique</option>
        <option value="vetements"    <?= $catFiltre==='vetements'    ? 'selected':'' ?>>Vêtements</option>
    </select>
    Prix max : <input type="number" name="prix_max" value="<?= $prixMax ?>" min="0">
    <button>Filtrer</button>
</form>
<h2><?= count($resultats) ?> produit(s) trouvé(s)</h2>
<ul><?php foreach ($resultats as $p): ?>
    <li><strong><?= htmlspecialchars($p['nom']) ?></strong> —
        <?= htmlspecialchars($p['categorie']) ?> —
        <strong><?= $p['prix'] ?> €</strong></li>
<?php endforeach; ?></ul>

🎯 Récapitulatif

  • ✅ GET via URL : ?clé=valeur&clé2=val2
  • ✅ $_GET : tableau, toujours des strings, ??
  • ✅ Formulaires GET : recherche, filtres
  • ✅ Conservation : value=, selected, checked
  • ✅ POST : données dans le corps de la requête
  • ✅ $_POST + REQUEST_METHOD pour détecter la soumission
  • ✅ XSS : injection JS — 3 types
  • ✅ htmlspecialchars() : sécuriser à l'affichage
  • ✅ filter_var(), ctype_digit(), preg_match()
  • ✅ Pattern PRG : éviter les doublons après POST

Points clés à retenir 📝

🔗 GET & POST

  • GET = lire · POST = modifier/créer
  • $_GET et $_POST → toujours des strings
  • Toujours ?? ou isset() avant de lire
  • REQUEST_METHOD === 'POST' pour détecter la soumission
  • Pattern PRG après tout POST réussi

🛡️ Sécurité

  • htmlspecialchars() à chaque affichage
  • Valider côté serveur (HTML seul insuffisant)
  • trim() avant toute validation
  • POST pour mots de passe et données perso
  • PDO + requêtes préparées pour la BDD

Prochaines étapes 🚀

  • 📤 Upload de fichiers : $_FILES, validation du type MIME
  • 🗄️ Bases de données : PDO, requêtes préparées (injection SQL)
  • 🔒 Authentification : système de login complet avec sessions
  • 🛡️ Tokens CSRF : protéger les formulaires POST
  • 📦 Sessions & Cookies : garder l'état entre les pages
💡 L'injection SQL est l'équivalent du XSS pour les bases de données. Sans requête préparée PDO, un champ de formulaire peut détruire toute votre base. À ne jamais oublier !

Ressources utiles 📚

  • 📖 php.net/manual/fr/ — Documentation officielle PHP
  • 🔐 owasp.org — Guide sécurité web (Top 10 des failles)
  • 🎓 developer.mozilla.org — HTML forms, méthodes HTTP
  • 💡 phptherightway.com — Bonnes pratiques PHP
  • 🔍 Chrome DevTools — Onglet Network pour inspecter les requêtes

Merci ! 👏

Des questions ?

🔗 GET · 📨 POST · 🛡️ Sécurité XSS

= Les fondations de l'interactivité web !