Comprendre et traiter les données d'un formulaire
Présentation
Les formulaires occupent une place importante dans le développement web. Ils permettent à un utilisateur d'envoyer des données à un script côté serveur, afin que l'application puisse les recevoir, les vérifier, les traiter, puis renvoyer une réponse adaptée.
On associe souvent les formulaires à une page de contact composée de plusieurs champs visibles, comme un nom, un prénom, un email et un bouton d'envoi. En pratique, un formulaire peut pourtant prendre des formes beaucoup plus variées.
On retrouve des formulaires lorsqu'il faut
- créer un compte
- se connecter
- rechercher un produit
- filtrer une liste
- envoyer un message
- publier un commentaire
- modifier un profil
- valider un panier
- confirmer une action
Un formulaire ne se limite donc pas à un écran avec de nombreux champs. Ce chapitre aborde la manière dont PHP reçoit les données envoyées par le navigateur, les valide et construit une réponse adaptée, quelle que soit la taille ou la forme du formulaire.
Pourquoi la validation côté client ne suffit pas
Les attributs HTML comme required, minlength, maxlength ou type="email", ainsi que les validations JavaScript, améliorent l'expérience utilisateur. Ils permettent d'afficher rapidement des retours à l'écran, sans attendre le serveur.
Ces vérifications sont utiles, mais elles ne suffisent jamais pour sécuriser une application.
Le navigateur appartient à l'utilisateur, celui-ci peut donc :
-
Modifier le HTML avec l'inspecteur du navigateur afin de contourner certaines vérifications.
Par exemple en supprimant l'attribut required ou en remplaçant type="email" par type="text". - Désactiver JavaScript.
- Envoyer une requête fabriquée manuellement avec un autre outil.
Autrement dit, le serveur ne doit jamais faire confiance au navigateur. Toute donnée reçue doit être considérée comme potentiellement absente, mal formée, modifiée ou malveillante.
Les vérifications côté client servent donc surtout à guider l'utilisateur. Les vérifications côté serveur servent à protéger l'application et à garantir la cohérence des données.
Les attributs de base du formulaire
Pour comprendre un formulaire HTML, trois attributs doivent être bien compris dès le départ. Les deux premiers se placent sur la balise <form>, le troisième sur chaque champ.
- action indique vers quelle URL le navigateur doit envoyer les données.
- method indique comment les données doivent être envoyées (GET ou POST).
- name donne un nom à chaque champ, afin que le serveur puisse retrouver sa valeur. Sans lui, le navigateur n'enverra pas la valeur du champ dans la requête.
<form action="/traiter-formulaire.php" method="post">
<div>
<label for="prenom">Prénom</label>
<input type="text" id="prenom" name="prenom">
</div>
<button type="submit">Envoyer</button>
</form>
Dans cet exemple, le navigateur enverra les données vers /traiter-formulaire.php en utilisant la méthode POST. La valeur saisie dans le champ sera accessible côté serveur avec la clé prenom provenant de l'attribut name.
La méthode GET
Avec la méthode GET, les données saisies dans le formulaire sont transmises au serveur et accessibles en PHP via la superglobale $_GET.
Fichier index.php
<form action="/recherche.php" method="get">
<div>
<label for="motCle">Mot-clé</label>
<input type="text" id="motCle" name="motCle">
</div>
<div>
<label for="categorie">Catégorie</label>
<input type="text" id="categorie" name="categorie">
</div>
<button type="submit">Rechercher</button>
</form>
Fichier recherche.php
<?php
$motCle = $_GET['motCle'] ?? '';
$categorie = $_GET['categorie'] ?? '';
echo $motCle;
echo '<br>';
echo $categorie;
Chaque clé de $_GET correspond à l'attribut name d'un champ du formulaire. L'opérateur ?? permet de définir une valeur par défaut si la clé est absente, ce qui évite une erreur PHP.
La chaîne de requête (query string)
Avec la méthode GET, les données ne transitent pas dans le corps de la requête HTTP. Elles sont ajoutées directement à l'URL, après un point d'interrogation ?. Cette partie de l'URL s'appelle la chaîne de requête, ou query string en anglais.
https://mon-site.test/recherche.php?motCle=recettes+de+cuisine&categorie=livres
Le premier paramètre commence juste après le ?. Les paramètres suivants sont séparés par le caractère &. Chaque paramètre suit le format nom=valeur.
L'encodage des caractères
Certains caractères ne peuvent pas apparaître tels quels dans une URL. Le navigateur les encode automatiquement avant d'envoyer la requête, selon deux règles principales.
- Les espaces sont remplacés par +
- Les caractères spéciaux (accents, symboles) sont remplacés par leur code hexadécimal précédé d'un %, par exemple %C3%A9 pour le caractère é
PHP décode ces valeurs automatiquement. Lorsqu'on lit $_GET['motCle'], on obtient la valeur originale, sans les caractères d'encodage.
Quand utiliser GET
La méthode GET est adaptée lorsqu'on souhaite demander une ressource ou filtrer un affichage, sans modifier durablement les données du serveur.
On l'utilise par exemple dans les situations suivantes :
- une recherche
- un filtre de produits
- un tri
- une pagination
Comme les paramètres font partie de l'URL, celle-ci peut être copiée, partagée, mémorisée dans les favoris ou revisitée avec les mêmes paramètres.
En revanche, GET ne convient pas pour transmettre des données sensibles comme un mot de passe ou une information personnelle, car elles apparaissent en clair dans la barre d'adresse. Cette visibilité rappelle aussi qu'un utilisateur peut modifier les paramètres manuellement, ce qui renforce l'importance de la validation côté serveur.
La méthode POST
Avec la méthode POST, les données saisies dans le formulaire sont transmises au serveur et accessibles en PHP via la superglobale $_POST.
Fichier index.php
<form action="/traiter-formulaire.php" method="post">
<div>
<label for="email">Email</label>
<input type="email" id="email" name="email">
</div>
<div>
<label for="motDePasse">Mot de passe</label>
<input type="password" id="motDePasse" name="motDePasse">
</div>
<button type="submit">Se connecter</button>
</form>
Fichier traiter-formulaire.php
<?php
$email = $_POST['email'] ?? '';
$motDePasse = $_POST['motDePasse'] ?? '';
echo $email;
echo '<br>';
echo $motDePasse;
Chaque clé de $_POST correspond à l'attribut name d'un champ du formulaire. L'opérateur ?? permet de définir une valeur par défaut si la clé est absente, ce qui évite une erreur PHP.
Les données dans le corps de la requête
Contrairement à GET, les données ne sont pas ajoutées à l'URL. Elles sont placées dans le corps de la requête HTTP, ce qui les rend invisibles dans la barre d'adresse.
Cela ne signifie pas pour autant qu'elles sont protégées. Sans HTTPS, les données transitent en clair sur le réseau. Elles restent aussi parfaitement lisibles dans les outils de développement du navigateur. La méthode POST rend les données moins visibles, elle ne les sécurise pas.
Quand utiliser POST
La méthode POST est adaptée lorsqu'une action modifie l'état de l'application, ou lorsque les données ne doivent pas apparaître dans l'URL.
On l'utilise par exemple dans les situations suivantes :
- se connecter
- créer un compte
- envoyer un message
- ajouter ou modifier une donnée
- supprimer un élément
Détecter la soumission d'un formulaire
Avant de lire une valeur dans $_GET ou $_POST, il faut d'abord vérifier si le formulaire a réellement été soumis. Cela évite d'essayer de lire des clés qui n'existent pas encore.
Connaître la méthode de la requête
PHP met à disposition la superglobale $_SERVER, un tableau associatif qui contient diverses informations sur la requête HTTP en cours et sur l'environnement du serveur.
La clé REQUEST_METHOD donne la méthode utilisée lors de la requête courante, sous forme de chaîne de caractères.
<?php
echo $_SERVER['REQUEST_METHOD']; // Affiche par exemple 'GET' ou 'POST'
Cette information permet d'adapter le traitement selon le type de requête reçu.
Détecter une soumission POST
Lorsque le formulaire utilise la méthode POST, tester $_SERVER['REQUEST_METHOD'] suffit à savoir s'il y a eu soumission. Une page ne reçoit une requête POST que si quelque chose l'a explicitement envoyée.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
echo 'Un formulaire a été soumis avec la méthode POST.';
}
Détecter une soumission GET
Avec GET, il faut être attentif à une chose importante. Lorsqu'un navigateur affiche simplement une page web, la requête envoyée au serveur utilise déjà la méthode GET.
Autrement dit, lorsqu'un utilisateur saisit une adresse dans la barre du navigateur, clique sur un lien ou recharge une page, le navigateur envoie normalement une requête GET. Il ne s'agit pas forcément d'un formulaire. C'est le fonctionnement habituel du web.
Par conséquent, tester uniquement $_SERVER['REQUEST_METHOD'] === 'GET' ne permet pas de savoir s'il y a réellement eu soumission du formulaire. Ce test sera vrai aussi bien lors d'une visite ordinaire que lors d'un envoi de formulaire en GET.
Pour faire la différence, il faut observer non seulement la méthode, mais aussi la présence de paramètres transmis dans l'URL. Lorsqu'un formulaire est soumis en GET, les données envoyées apparaissent dans l'URL et PHP les place dans la superglobale $_GET.
La fonction empty() permet de vérifier si une valeur est vide. Appliquée à un tableau, elle retourne true si ce tableau ne contient aucune entrée, et false dès qu'il contient au moins un élément.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET))
{
echo 'Le formulaire a été soumis en GET.';
}
Notez que empty() convient bien pour vérifier si un tableau est vide. En revanche, cette fonction n'est pas adaptée pour valider directement une saisie utilisateur, car elle considère aussi comme vides certaines valeurs comme '0'. Par exemple, si un champ demande le nombre d'enfants et que l'utilisateur saisit 0, cette valeur pourrait être interprétée à tort comme une absence de saisie. Dans ce cas, il vaut mieux vérifier explicitement si la valeur reçue est une chaîne vide, par exemple avec $entreeUtilisateur === ''.
Validation des entrées utilisateur côté serveur
Les attributs HTML comme required, minlength, maxlength ou type="email" sont utiles, car ils améliorent le confort d'utilisation. Le navigateur peut ainsi signaler rapidement certaines erreurs évidentes.
Malgré cela, le serveur doit toujours refaire ses propres vérifications. En effet, les contrôles réalisés dans le navigateur ne sont pas suffisamment fiables pour prendre une décision importante, comme enregistrer une donnée, créer un compte ou envoyer un message.
Une personne peut par exemple modifier le HTML avec les outils du navigateur, désactiver JavaScript, ou fabriquer elle-même une requête HTTP sans passer par le formulaire prévu. Le serveur reste donc le dernier endroit où une décision fiable peut être prise.
Lire et nettoyer les entrées utilisateur
Lorsqu'un formulaire est envoyé, il est fréquent qu'une clé attendue ne soit pas présente. Cela peut arriver si un champ est absent, mal nommé, supprimé du HTML, ou tout simplement non envoyé. Accéder directement au contenu de l'une de ces valeurs sans précaution provoquerait une erreur PHP si la clé n'existe pas.
L'opérateur ??, appelé opérateur de fusion null, permet de gérer ce cas proprement. Il retourne la valeur de gauche si elle existe et n'est pas null, et la valeur de droite dans tous les autres cas.
<?php
$nom = $_POST['nom'] ?? '';
Si la clé nom est présente dans $_POST, sa valeur est utilisée. Sinon, on obtient une chaîne vide.
Une fois la valeur récupérée, il est recommandé de la nettoyer avant de l'utiliser. La fonction trim() supprime les espaces et les retours à la ligne en début et en fin de chaîne. Un utilisateur peut en effet saisir des espaces involontaires, voire ne saisir que des espaces. Sans trim(), une valeur composée uniquement d'espaces ne serait pas détectée comme vide, ce qui fausserait les vérifications qui suivent.
<?php
$nom = trim($_POST['nom'] ?? '');
Cette habitude de lire et nettoyer les entrées dès leur réception rend le code plus robuste et prépare correctement les données avant leur validation.
Commencer avec un champ obligatoire et un champ facultatif
Pour bien comprendre la logique de validation, il est préférable de commencer par un cas simple.
Prenons deux champs :
- nom est obligatoire
- prenom est facultatif
Fichier index.php
<form action="traiter-formulaire.php" method="post">
<div>
<label for="nom">Nom</label>
<input
type="text"
id="nom"
name="nom"
required
>
</div>
<div>
<label for="prenom">Prénom</label>
<input
type="text"
id="prenom"
name="prenom"
>
</div>
<button type="submit">Envoyer</button>
</form>
La règle n'est pas la même pour les deux. Un champ obligatoire doit contenir une valeur. Un champ facultatif peut rester vide, mais si l'utilisateur écrit quelque chose, cette valeur doit tout de même être lue proprement et traitée correctement.
Fichier traiter-formulaire.php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
// Lire les valeurs envoyées par le formulaire.
// Si une clé n'existe pas dans $_POST, on utilise une chaîne vide.
$nom = trim($_POST['nom'] ?? '');
$prenom = trim($_POST['prenom'] ?? '');
// Le nom est obligatoire.
// Une chaîne vide signifie ici qu'aucune valeur exploitable n'a été saisie.
if ($nom === '')
{
echo 'Le nom est requis.<br>';
}
else
{
echo 'Le nom a bien été reçu.<br>';
}
// Le prénom est facultatif.
// On ne signale donc quelque chose que si l'utilisateur a réellement écrit une valeur.
if ($prenom !== '')
{
echo 'Le prénom a bien été reçu.<br>';
}
}
Dans cet exemple, l'absence de valeur est une erreur pour nom, mais pas pour prenom.
Ajouter ensuite des règles de longueur
Une fois la différence entre champ obligatoire et champ facultatif comprise, on peut ajouter d'autres règles, comme une longueur minimale ou maximale.
Le plus simple consiste à vérifier d'abord si un champ obligatoire est vide. Ensuite seulement, si une valeur est présente, on peut contrôler sa longueur.
Fichier traiter-formulaire.php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
$nom = trim($_POST['nom'] ?? '');
$prenom = trim($_POST['prenom'] ?? '');
if ($nom === '')
{
echo 'Le nom est requis.<br>';
}
elseif (mb_strlen($nom) < 2 || mb_strlen($nom) > 100)
{
// mb_strlen() permet de compter correctement les caractères,
// y compris les caractères accentués.
echo 'Le nom doit contenir entre 2 et 100 caractères.<br>';
}
else
{
echo 'Le nom est valide.<br>';
}
// Le champ complément reste facultatif.
if ($prenom !== '')
{
// On vérifie sa longueur uniquement si l'utilisateur a saisi quelque chose.
if (mb_strlen($prenom) > 255)
{
echo 'Le complément ne peut pas dépasser 255 caractères.<br>';
}
else
{
echo 'Le prénom a bien été reçu.<br>';
}
}
}
Cette manière de procéder permet d'éviter des messages incohérents. Un champ vide ne doit pas déclencher en même temps une erreur de longueur minimale et une erreur d'absence de saisie.
Ajouter un champ email
Après les règles de présence et de longueur, on peut introduire un contrôle de format. L'exemple classique est l'adresse email.
Extrait du formulaire
<div>
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
required
>
</div>
Extrait du traitement
<?php
$email = trim($_POST['email'] ?? '');
if ($email === '')
{
echo 'L\'email est requis.<br>';
}
elseif (!filter_var($email, FILTER_VALIDATE_EMAIL))
{
// filter_var() vérifie ici si le format ressemble à une adresse email valide.
echo 'Veuillez saisir une adresse email valide.<br>';
}
else
{
echo 'L\'email est valide.<br>';
}
La fonction filter_var() prend deux arguments. Le premier est la valeur à tester. Le second est un filtre qui définit le type de vérification à effectuer. Ici, FILTER_VALIDATE_EMAIL indique à PHP de vérifier si la valeur ressemble à une adresse email valide selon les règles de format standard. La fonction retourne la valeur si elle est valide, ou false si elle ne l'est pas. Le ! devant inverse ce résultat pour entrer dans le bloc d'erreur.
PHP propose d'autres filtres du même type pour d'autres formats courants, par exemple FILTER_VALIDATE_URL pour vérifier qu'une valeur ressemble à une adresse web valide. La logique reste identique, on appelle filter_var() avec la valeur et le filtre correspondant, et on traite le résultat. Pour d'autres types de données comme les entiers ou les décimaux, d'autres approches existent et seront abordées dans la section sur la validation.
Afficher les messages au bon endroit
Jusqu'ici, le formulaire pointait vers un fichier de traitement via l'attribut action. Lors de la soumission, le navigateur quittait donc la page du formulaire pour charger ce fichier. Les messages étaient affichés là où les echo se trouvaient, sans lien avec la mise en page du formulaire.
Cette approche convient pour comprendre le mécanisme général, mais elle devient vite contraignante :
- Les messages apparaissent à l'endroit où les echo sont exécutés, et non sous les champs concernés.
- L'utilisateur quitte la page du formulaire pour arriver sur une page de traitement distincte.
Pour résoudre ce problème, on peut exécuter le traitement avant l'affichage du formulaire, dans le même flux d'exécution. Les variables créées pendant ce traitement restent alors disponibles pour la suite de la page.
Concrètement, au lieu de cibler le fichier de traitement dans l'attribut action, on fait pointer le formulaire vers la page courante, et on inclut le fichier de traitement en haut du fichier d'affichage via require_once, avant la première ligne de HTML.
Ce placement en amont du HTML est important pour deux raisons. D'une part, les variables préparées par le traitement sont déjà disponibles au moment où la page commence à s'afficher. D'autre part, si le script doit envoyer un en-tête HTTP, démarrer une session ou effectuer une redirection, cela doit obligatoirement se faire avant toute sortie HTML.
Dans un projet plus structuré, un routeur ou un contrôleur joue ce rôle. Cette approche avec require_once n'est donc pas une fin en soi, mais une étape intermédiaire utile pour comprendre la séparation entre traitement et affichage.
Fichier traiter-formulaire.php
<?php
// Préparer les variables qui serviront à stocker les messages éventuels.
// $erreurs contiendra les messages d'erreur liés aux champs.
// $messageGlobal servira à afficher un message général pour le formulaire.
$erreurs = [];
$messageGlobal = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
$nom = trim($_POST['nom'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if ($nom === '')
{
// En cas d'erreur, on n'affiche plus le message directement avec echo.
// On le stocke dans le tableau $erreurs afin de pouvoir l'afficher plus tard
// à l'endroit voulu dans le formulaire.
$erreurs['nom'] = 'Le nom est requis.';
}
elseif (mb_strlen($nom) < 2 || mb_strlen($nom) > 100)
{
$erreurs['nom'] = 'Le nom doit contenir entre 2 et 100 caractères.';
}
if ($email === '')
{
$erreurs['email'] = 'L\'email est requis.';
}
elseif (!filter_var($email, FILTER_VALIDATE_EMAIL))
{
$erreurs['email'] = 'Veuillez saisir une adresse email valide.';
}
if ($message === '')
{
$erreurs['message'] = 'Le message est requis.';
}
elseif (mb_strlen($message) < 10 || mb_strlen($message) > 3000)
{
$erreurs['message'] = 'Le message doit contenir entre 10 et 3000 caractères.';
}
// Un tableau $erreurs vide signifie qu'aucune erreur n'a été trouvée.
// On prépare alors le message global à afficher sous le formulaire.
if (empty($erreurs))
{
$messageGlobal = 'Le formulaire a bien été envoyé.';
}
else
{
$messageGlobal = 'Le formulaire contient des erreurs.';
}
}
Fichier index.php
<?php require_once __DIR__ . '/traiter-formulaire.php'; ?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact</title>
</head>
<body>
<!--
Lorsque l'attribut HTML "action" n'est pas précisé,
le formulaire est envoyé vers la même page que celle qui l'affiche.
-->
<form method="post">
<div>
<label for="nom">Nom</label>
<input type="text" id="nom" name="nom" minlength="2" maxlength="100" required>
<div class="message-erreur">
<!--
Affiche le message d'erreur du champ "nom".
Si aucun message n'existe pour ce champ,
rien n'est affiché.
-->
<?= $erreurs['nom'] ?? '' ?>
</div>
</div>
<div>
<label for="prenom">Prénom</label>
<input type="text" id="prenom" name="prenom">
<div class="message-erreur">
<?= $erreurs['prenom'] ?? '' ?>
</div>
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<div class="message-erreur">
<?= $erreurs['email'] ?? '' ?>
</div>
</div>
<button type="submit">Envoyer</button>
<div class="message-global">
<?= $messageGlobal ?? '' ?>
</div>
</form>
</body>
</html>
Dans cet exemple, les messages d'erreur et le message global ne sont pas passés dans htmlspecialchars(), car leur contenu est entièrement défini dans le code PHP. Ils ne proviennent donc pas d'une saisie utilisateur ni d'une source extérieure. Dans ce cas précis, le navigateur ne reçoit que des chaînes écrites par le développeur, ce qui ne présente pas de risque particulier.
Réafficher les valeurs en cas d'erreur
Lorsqu'un formulaire contient des erreurs, l'utilisateur doit les corriger et soumettre à nouveau. Si les champs sont vides à ce moment, il doit tout ressaisir depuis le début. C'est un inconfort réel, particulièrement sur un formulaire avec plusieurs champs.
La pratique habituelle consiste à conserver les valeurs saisies et à les remettre dans les champs lors du rechargement de la page. Pour cela, on stocke les valeurs lues depuis $_POST dans un tableau dédié, disponible ensuite dans la vue.
Ajout dans traiter-formulaire.php
<?php
$erreurs = [];
$messageGlobal = '';
$anciennesValeurs = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
$nom = trim($_POST['nom'] ?? '');
$prenom = trim($_POST['prenom'] ?? '');
$email = trim($_POST['email'] ?? '');
// ... validations identiques à la section précédente
// Si le tableau $erreurs contient quelque chose, cela signifie que la validation a échouée.
if (empty($erreurs))
{
$messageGlobal = 'Le formulaire a bien été envoyé.';
}
else
{
$messageGlobal = 'Le formulaire contient des erreurs.';
// Comme le formulaire contient des erreurs, on conserve les valeurs déjà saisies
// afin de pouvoir les réafficher dans les champs lors du nouveau rendu de la page.
$anciennesValeurs['nom'] = $nom;
$anciennesValeurs['prenom'] = $prenom;
$anciennesValeurs['email'] = $email;
}
}
Dans la vue, on injecte ces valeurs dans les attributs value des champs.
Extrait de index.php
<input
type="text"
id="nom"
name="nom"
minlength="2"
maxlength="100"
value="<?= htmlspecialchars($anciennesValeurs['nom'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
required
>
L'échappement avec htmlspecialchars() est ici indispensable. La valeur étant réinjectée directement dans le HTML, sans échappement, une saisie contenant des guillemets ou des chevrons pourrait casser la structure de la page ou introduire du code malveillant.
Cas particuliers lors du réaffichage
Réafficher une valeur dans une balise textarea
Pour une balise <textarea>, la valeur ne se place pas dans un attribut value, mais entre la balise ouvrante et la balise fermante.
Exemple de réaffichage d'une valeur dans une balise textarea
<textarea
id="message"
name="message"
minlength="10"
maxlength="3000"
required
><?= htmlspecialchars($anciennesValeurs['message'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></textarea>
Ne pas réafficher un mot de passe
Le réaffichage des valeurs déjà saisies est utile pour la plupart des champs, mais il ne doit pas être appliqué aux mots de passe. Même si un champ de type password masque les caractères à l'écran, la valeur reste présente en clair dans le HTML si elle est replacée dans l'attribut value.
Le problème ne vient pas de ce que l'utilisateur voit à l'écran, mais de ce que le serveur a écrit dans le HTML transmis au navigateur. Effacer le contenu du champ visuellement ne change rien, la valeur est déjà présente dans le code source de la page, qui peut être consulté à tout moment via les outils de développement, indépendamment de ce qui est affiché dans le champ.
Concrètement, plusieurs situations posent problème.
- quelqu'un consulte le code source ou les outils de développement sur le poste de l'utilisateur avant qu'il ne navigue vers une autre page
- le navigateur met la page en cache et cette version contient le mot de passe dans le HTML
- une extension de navigateur ou un script malveillant lit le contenu du DOM, où la valeur est accessible même si le champ est de type password
Par convention, lorsqu'un formulaire contient une erreur, l'utilisateur ressaisit toujours son mot de passe. Les navigateurs n'essaient d'ailleurs pas de pré-remplir les champs type="password" dans ce contexte.
Autres types de champs
Les formulaires ne contiennent pas uniquement des champs texte. Il est donc nécessaire de savoir récupérer, valider et réafficher d'autres types d'entrées.
Le principe reste le même que dans la section précédente. Lorsqu'un formulaire contient au moins une erreur, les valeurs déjà reçues peuvent être conservées dans le tableau $anciennesValeurs. La vue peut ensuite s'appuyer sur ce tableau pour remettre le bon attribut selected ou checked au bon endroit.
Dans les extraits de traitement ci-dessous, seule la validation du champ concerné est montrée. Dans un formulaire complet, la copie vers $anciennesValeurs se fait ensuite selon le même principe que dans la section précédente, uniquement si le formulaire contient au moins une erreur.
Le champ select simple
Un champ select simple permet de choisir une seule option parmi plusieurs.
Extrait de index.php
<label for="pays">Pays</label>
<select id="pays" name="pays">
<option value="">-- Choisir --</option>
<!--
Si la valeur précédemment soumise pour "pays" est "belgique",
on ajoute l'attribut "selected" pour conserver la sélection
après une erreur de validation.
-->
<option
value="belgique"
<?= ($anciennesValeurs['pays'] ?? '') === 'belgique' ? 'selected' : '' ?>
>Belgique</option>
<option
value="france"
<?= ($anciennesValeurs['pays'] ?? '') === 'france' ? 'selected' : '' ?>
>France</option>
<option
value="canada"
<?= ($anciennesValeurs['pays'] ?? '') === 'canada' ? 'selected' : '' ?>
>Canada</option>
</select>
<div class="message-erreur">
<?= $erreurs['pays'] ?? '' ?>
</div>
Le réaffichage se fait ici en ajoutant l'attribut selected sur l'option correspondant à la valeur conservée.
Extrait de traiter-formulaire.php
<?php
$paysAutorises = ['belgique', 'france', 'canada'];
$pays = $_POST['pays'] ?? '';
if ($pays === '')
{
$erreurs['pays'] = 'Veuillez choisir un pays.';
}
elseif (!in_array($pays, $paysAutorises, true))
{
// Vérifier que la valeur reçue correspond exactement
// à l'un des pays autorisés.
// Le troisième argument active une comparaison stricte.
$erreurs['pays'] = 'La valeur envoyée pour le pays est invalide.';
}
Les boutons radio
Les boutons radio permettent de choisir une seule valeur dans un groupe. Tous les boutons d'un même groupe partagent le même attribut name.
Extrait de index.php
<p>Niveau</p>
<!--
Si la valeur précédemment soumise pour "niveau" est "debutant",
on ajoute l'attribut "checked" pour conserver la sélection
après une erreur de validation.
-->
<div>
<input
type="radio"
id="niveau-debutant"
name="niveau"
value="debutant"
<?= ($anciennesValeurs['niveau'] ?? '') === 'debutant' ? 'checked' : '' ?>
>
<label for="niveau-debutant">Débutant</label>
</div>
<div>
<input
type="radio"
id="niveau-intermediaire"
name="niveau"
value="intermediaire"
<?= ($anciennesValeurs['niveau'] ?? '') === 'intermediaire' ? 'checked' : '' ?>
>
<label for="niveau-intermediaire">Intermédiaire</label>
</div>
<div>
<input
type="radio"
id="niveau-avance"
name="niveau"
value="avance"
<?= ($anciennesValeurs['niveau'] ?? '') === 'avance' ? 'checked' : '' ?>
>
<label for="niveau-avance">Avancé</label>
</div>
<div class="message-erreur">
<?= $erreurs['niveau'] ?? '' ?>
</div>
Le réaffichage repose cette fois sur l'attribut checked. Le navigateur recochera automatiquement le bouton qui porte cet attribut.
Extrait de traiter-formulaire.php
<?php
$niveauxAutorises = ['debutant', 'intermediaire', 'avance'];
$niveau = $_POST['niveau'] ?? '';
if ($niveau === '')
{
$erreurs['niveau'] = 'Veuillez choisir un niveau.';
}
elseif (!in_array($niveau, $niveauxAutorises, true))
{
$erreurs['niveau'] = 'La valeur envoyée pour le niveau est invalide.';
}
La case à cocher unique
Une case à cocher unique sert souvent à confirmer une condition, comme l'acceptation d'un règlement. Si elle n'est pas cochée, sa clé n'est pas envoyée dans $_POST.
Extrait de index.php
<!--
Si la valeur précédemment soumise pour "reglement" est "oui",
on ajoute l'attribut "checked" pour conserver la sélection
après une erreur de validation.
-->
<div>
<input
type="checkbox"
id="reglement"
name="reglement"
value="oui"
<?= ($anciennesValeurs['reglement'] ?? '') === 'oui' ? 'checked' : '' ?>
>
<label for="reglement">J'accepte le règlement</label>
</div>
<div class="message-erreur">
<?= $erreurs['reglement'] ?? '' ?>
</div>
Si la case était cochée avant la soumission, l'attribut checked permet de la recocher lors du nouveau rendu de la page.
Extrait de traiter-formulaire.php
<?php
$reglement = $_POST['reglement'] ?? '';
if ($reglement !== 'oui')
{
$erreurs['reglement'] = 'Vous devez accepter le règlement.';
}
Les cases à cocher multiples
Lorsqu'un utilisateur peut cocher plusieurs options, le nom du champ doit se terminer par []. Le navigateur enverra alors un tableau de valeurs.
Extrait de index.php
<p>Centres d'intérêt</p>
<!--
Le name "hobbies[]" indique que plusieurs valeurs peuvent être envoyées
pour ce même champ. PHP les regroupera alors dans le tableau $_POST['hobbies'].
Si la valeur "lecture" faisait partie des choix cochés lors de la
soumission précédente, on ajoute l'attribut "checked" afin de
conserver cette case cochée après une erreur de validation.
-->
<div>
<input
type="checkbox"
id="hobbies-lecture"
name="hobbies[]"
value="lecture"
<?= in_array('lecture', $anciennesValeurs['hobbies'] ?? []) ? 'checked' : '' ?>
>
<label for="hobbies-lecture">Lecture</label>
</div>
<div>
<input
type="checkbox"
id="hobbies-sport"
name="hobbies[]"
value="sport"
<?= in_array('sport', $anciennesValeurs['hobbies'] ?? []) ? 'checked' : '' ?>
>
<label for="hobbies-sport">Sport</label>
</div>
<div>
<input
type="checkbox"
id="hobbies-musique"
name="hobbies[]"
value="musique"
<?= in_array('musique', $anciennesValeurs['hobbies'] ?? []) ? 'checked' : '' ?>
>
<label for="hobbies-musique">Musique</label>
</div>
<div class="message-erreur">
<?= $erreurs['hobbies'] ?? '' ?>
</div>
Ici, la valeur conservée dans $anciennesValeurs['hobbies'] doit être un tableau. Le réaffichage consiste à vérifier, case par case, si la valeur correspondante se trouve dans ce tableau.
Extrait de traiter-formulaire.php
<?php
$hobbiesAutorises = ['lecture', 'sport', 'musique'];
$hobbies = $_POST['hobbies'] ?? [];
if (!is_array($hobbies))
{
$hobbies = [];
}
if (empty($hobbies))
{
$erreurs['hobbies'] = 'Veuillez sélectionner au moins un centre d\'intérêt.';
}
else
{
foreach ($hobbies as $hobby)
{
if (!in_array($hobby, $hobbiesAutorises, true))
{
$erreurs['hobbies'] = 'Une valeur envoyée pour les centres d\'intérêt est invalide.';
break;
}
}
}
Le select multiple
Un champ select peut lui aussi permettre plusieurs sélections, à condition d'ajouter l'attribut multiple et d'utiliser un nom terminé par [].
Extrait de index.php
<label for="langages">Langages préférés</label>
<!--
Le name "langages[]" indique que plusieurs valeurs peuvent être envoyées
pour ce même champ. PHP les regroupera alors dans le tableau $_POST['langages'].
Si la valeur "html" faisait partie des choix sélectionnés lors de la
soumission précédente, on ajoute l'attribut "selected" afin de
conserver cette sélection après une erreur de validation.
-->
<select id="langages" name="langages[]" multiple>
<option
value="html"
<?= in_array('html', $anciennesValeurs['langages'] ?? []) ? 'selected' : '' ?>
>HTML</option>
<option
value="css"
<?= in_array('css', $anciennesValeurs['langages'] ?? []) ? 'selected' : '' ?>
>CSS</option>
<option
value="javascript"
<?= in_array('javascript', $anciennesValeurs['langages'] ?? []) ? 'selected' : '' ?>
>JavaScript</option>
<option
value="php"
<?= in_array('php', $anciennesValeurs['langages'] ?? []) ? 'selected' : '' ?>
>PHP</option>
</select>
<div class="message-erreur">
<?= $erreurs['langages'] ?? '' ?>
</div>
Le principe est le même que pour les cases à cocher multiples. Lorsqu'un champ peut renvoyer plusieurs valeurs, PHP doit être prêt à recevoir un tableau, et la vue doit vérifier option par option ce qu'il faut resélectionner.
Extrait de traiter-formulaire.php
<?php
$langagesAutorises = ['html', 'css', 'javascript', 'php'];
$langages = $_POST['langages'] ?? [];
if (!is_array($langages))
{
$langages = [];
}
if (empty($langages))
{
$erreurs['langages'] = 'Veuillez sélectionner au moins un langage.';
}
else
{
// Parcourir toutes les langues sélectionnées.
foreach ($langages as $langage)
{
// Si l'une des langues sélectionnée n'est pas présente
// dans le tableau des langues autorisées,
// on ajoute le message d'erreur et on interrompt la boucle.
if (!in_array($langage, $langagesAutorises, true))
{
$erreurs['langages'] = 'Une valeur envoyée pour les langages est invalide.';
break;
}
}
}
Les champs cachés
Un champ caché permet d'envoyer une information au serveur sans l'afficher visuellement dans le formulaire. L'utilisateur ne voit pas le champ, mais sa valeur est bien transmise lors de la soumission.
Le cas d'usage le plus courant est la transmission d'un identifiant technique, par exemple lors de la modification d'un enregistrement. Le serveur a besoin de savoir quel élément modifier, mais l'utilisateur n'a pas à saisir cet identifiant lui-même.
Extrait de index.php
<form method="post">
<input type="hidden" name="id" value="42">
<div>
<label for="nom">Nom</label>
<input type="text" id="nom" name="nom" value="Dupont">
</div>
<button type="submit">Enregistrer</button>
</form>
Extrait de traiter-formulaire.php
<?php
// ctype_digit() vérifie que la valeur ne contient que des chiffres (pas de négatif, pas de décimal).
// Si la vérification échoue ou que la clé est absente, on stocke null plutôt qu'une valeur douteuse.
// Sinon, on converti la chaîne en entier.
$id = ctype_digit($_POST['id'] ?? '') ? (int) $_POST['id'] : null;
if ($id === null)
{
$erreurs['id'] = 'Identifiant absent ou invalide.';
}
Un champ caché n'est pas plus fiable qu'un champ visible. Il est simplement absent de l'interface. Sa valeur peut être modifiée par n'importe qui via les outils de développement du navigateur. Il ne faut donc jamais lui faire confiance sans vérification côté serveur.
Le POST-Redirect-GET
La méthode actuelle présente une limite. Après une soumission réussie, la page affichée est toujours le résultat direct de la requête POST. Si l'utilisateur actualise la page après cette soumission, le navigateur propose de renvoyer les données du formulaire, ce qui peut provoquer une double soumission.
La solution classique consiste à terminer le traitement POST par une redirection vers une page chargée ensuite en GET. Cette séquence s'appelle le POST-Redirect-GET, ou PRG. Une fois la redirection effectuée, actualiser la page ne fait que rejouer la requête GET finale, sans risque de double soumission.
Pour rediriger sans coder l'URL en dur, on peut utiliser $_SERVER['REQUEST_URI'], qui contient l'URL de la page en cours.
<?php
// Demander au navigateur d'effectuer une redirection vers l'URL courante.
// header() permet d'envoyer un en-tête HTTP dans la réponse du serveur.
// Ici, l'en-tête "Location" indique au navigateur quelle URL il doit charger.
// $_SERVER['REQUEST_URI'] contient le chemin actuellement demandé.
header('Location: ' . $_SERVER['REQUEST_URI']);
// Arrêter immédiatement l'exécution du script.
// Sans exit, le code situé plus bas pourrait continuer à s'exécuter
// alors que la redirection a déjà été demandée.
exit;
Nous n'appliquerons pas encore cette technique ici. Dès qu'une redirection intervient, une nouvelle requête commence avec son propre contexte d'exécution. Les variables préparées pendant le traitement POST, comme $erreurs ou $messageGlobal, ne sont plus disponibles automatiquement dans la page suivante.
Pour les faire traverser la redirection, il faut un mécanisme permettant au serveur de conserver temporairement des données entre deux requêtes pour un navigateur donné. C'est précisément ce que permettent les sessions, que nous étudierons dans un chapitre ultérieur.
Exercices - Part 01
Exercice 01
Cet exercice a pour objectif de vous familiariser progressivement avec le traitement des formulaires en PHP. À travers une série d'étapes guidées, vous allez découvrir comment un formulaire transmet des données au serveur, comment PHP les récupère, puis comment traiter ces données de manière de plus en plus rigoureuse.
Au fil de l'exercice, vous apprendrez notamment à travailler avec les méthodes GET et POST, à comprendre comment détecter côté serveur si une soumission a eu lieu, à valider les valeurs reçues selon certaines règles, à afficher les messages d'erreur au bon endroit et à réafficher les anciennes valeurs en cas d'erreur afin d'améliorer le confort d'utilisation.
Dans les premières étapes, vous allez volontairement commencer par des versions très simples, parfois même incomplètes. Le but est de faire apparaître certains comportements de PHP de manière concrète, afin de pouvoir ensuite les comprendre, les corriger et les améliorer.
L'objectif n'est donc pas de construire immédiatement une application complète, mais d'avancer étape par étape pour comprendre ce qui se passe réellement entre le navigateur, le formulaire et le serveur.
Structure
Créer un dossier nommé Exo-comprendre-et-traiter-donnees-formulaire-01. Toutes les étapes de cet exercice seront réalisées dans ce même dossier de travail. Au fur et à mesure, vous modifierez ces fichiers, sans repartir de zéro à chaque nouvelle étape.
Débuter l'exercice avec la structure suivante :
📁 Exo-comprendre-et-traiter-donnees-formulaire-01/
├── 📁 traitements/
│ └── traitement-form.php
└── 📄 index.php
Étape 01
Créer un premier formulaire très simple envoyé en GET et observer comment les données transmises apparaissent dans l'URL puis dans $_GET. Dans cette étape, le but n'est pas encore de sécuriser ou de valider quoi que ce soit. Il s'agit d'abord de voir concrètement ce que reçoit le serveur et ce qui se passe lorsqu'une clé attendue n'existe pas.
-
Dans index.php, créer un formulaire HTML.
- Donner à l'attribut action la valeur traitement-form.php.
- Donner à l'attribut method la valeur get.
-
Dans ce formulaire, ajouter deux champs texte avec leurs libellés.
- Un premier champ pour le prénom.
- Un second champ pour le surnom.
- Le champ prenom doit être obligatoire avec l'attribut required.
- Le champ surnom doit rester facultatif.
- Donner au premier champ le name prenom.
- Donner au second champ le name surnom.
- Ajouter un bouton de soumission avec le texte Envoyer.
- Dans traitement-form.php, afficher directement avec echo le texte Prénom saisi : suivi du prénom entré, puis le texte Surnom saisi : suivi du surnom entré. À ce stade, ne faire encore aucune vérification.
-
Tester d'abord le cas le plus simple.
- Dans le navigateur, accéder au formulaire via l'URL locale du projet par exemple avec localhost ou avec le nom de domaine du virtual host configuré.
- Remplir les deux champs.
- Soumettre le formulaire.
- Vérifier que le navigateur charge bien traitement-form.php.
- Observer que l'URL contient maintenant les deux paramètres transmis en GET.
- Vérifier que les deux valeurs affichées correspondent bien à celles qui ont été saisies.
-
Modifier ensuite manuellement les paramètres dans l'URL.
- Dans la barre d'adresse, remplacer la valeur de prenom et/ou celle de surnom par d'autres valeurs.
- Valider ensuite avec la touche Entrée.
- Observer que les valeurs affichées changent elles aussi.
- Constater que, avec la méthode GET, les données lues par PHP proviennent directement des paramètres présents dans l'URL.
-
Tester ensuite l'accès direct à la page de traitement sans paramètre.
- Dans la barre d'adresse du navigateur, afficher directement traitement-form.php sans ajouter de query string.
- Observer le comportement obtenu.
- Une erreur apparaît, car le script essaie d'afficher des clés de $_GET qui n'existent pas.
-
Ajouter alors une première protection globale.
- Dans traitement-form.php, vérifier d'abord que la requête utilise bien la méthode GET.
- Vérifier également que $_GET n'est pas vide.
- N'afficher les messages que si ces deux conditions sont respectées. Dans le cas contraire, afficher un message indiquant qu'aucune soumission n'a été réalisée.
- Cette vérification évite d'essayer de lire des données lorsqu'aucun paramètre n'a été transmis.
-
Refaire les tests précédents.
- Revenir d'abord au formulaire, saisir une valeur dans les deux champs puis soumettre.
- Vérifier que la page traitement-form.php s'affiche correctement avec les valeurs transmises.
- Afficher ensuite à nouveau la page traitement-form.php sans query string.
- S'assurer que le message indiquant qu'aucune soumission n'a été réalisée s'affiche bien.
Étape 02
- Revenir maintenant au formulaire à la racine du site.
- Faire un nouveau test en ne remplissant que le champ obligatoire prenom puis en laissant le champ surnom vide.
- Soumettre le formulaire et observer le comportement obtenu. Une erreur apparaît encore. C'est normal, car la vérification précédente contrôlait seulement que la requête était bien en GET et que $_GET n'était pas vide. Or ici, $_GET contient bien une donnée puisque le prénom a été envoyé, mais cela ne garantit pas que chaque clé attendue existe réellement.
-
Ajouter alors une récupération individuelle pour chaque champ.
- Créer une variable $prenom contenant $_GET['prenom'] si celui-ci existe, sinon lui attribuer une chaîne vide ('').
- Créer une variable $surnom contenant $_GET['surnom'] si celui-ci existe, sinon lui attribuer une chaîne vide ('').
- Remplacer ensuite dans les echo les accès directs à $_GET['prenom'] et $_GET['surnom'] par les variables $prenom et $surnom.
-
Adapter ensuite l'affichage.
- Afficher le message du prénom uniquement si $prenom n'est pas une chaîne de caractères vide.
- Afficher le message du surnom uniquement si $surnom n'est pas une chaîne de caractères vide.
- Cette fois, si un champ facultatif n'est pas transmis, le script ne tente plus d'afficher une clé absente.
-
Refaire le test en remplissant seulement le prénom.
- Vérifier que le prénom s'affiche correctement.
- Vérifier qu'aucune erreur ne s'affiche pour le surnom absent.
-
Faire maintenant un test de contournement du HTML.
- Revenir sur le formulaire.
- Ouvrir les outils de développement du navigateur.
- Supprimer l'attribut required du champ prenom via l'inspecteur.
- Soumettre ensuite le formulaire avec les deux champs vides.
-
Observer le résultat obtenu.
- La page de traitement ne provoque plus d'erreur.
- Elle n'affiche rien non plus.
- C'est logique. Plus haut, pour éviter de provoquer des erreurs lorsqu'une entrée n'existe pas, nous avons remplacé les valeurs absentes par une chaîne vide. Les entrées utilisateur ne sont donc affichées que lorsqu'elles contiennent une valeur différente d'une chaîne vide.
- Pourtant, cela ne nous convient pas pour prenom, car ce champ est censé être obligatoire.
-
Ajouter alors une vraie vérification serveur pour le champ requis.
- Si $prenom, issu du champ requis prenom, est vide, afficher un message d'erreur indiquant que le prénom est obligatoire.
- Sinon, afficher le message normal avec le prénom saisi.
- Ne pas faire la même chose pour $surnom, car ce champ reste facultatif.
-
Refaire enfin les tests suivants.
- Soumettre le formulaire avec les deux champs vides après avoir retiré required dans l'inspecteur.
- Vérifier qu'un message d'erreur s'affiche bien pour le prénom.
- Revenir ensuite au formulaire et saisir une valeur dans le champ prenom.
- Vérifier que le message normal s'affiche correctement.
Étape 03
Jusqu'ici, une chaîne vide était déjà correctement gérée. Il reste toutefois un cas particulier, celui où une saisie est composée uniquement d'espaces. Techniquement, ce type de saisie contient bien des caractères. Elle peut donc être considérée comme remplie, aussi bien côté navigateur que côté serveur. Pourtant, dans la plupart des formulaires, ce type de valeur n'est pas souhaité. Dans cette étape, l'objectif est donc de nettoyer les entrées reçues avant de les vérifier.
- Dans le navigateur, revenir sur le formulaire situé à la racine du site.
-
Faire un premier test en saisissant uniquement des espaces dans le champ requis prenom, tout en respectant les contraintes de longueur minimale et maximale.
- Laisser le champ surnom vide.
- Soumettre ensuite le formulaire.
- Observer le comportement obtenu. La phrase Prénom saisi : suivie des espaces entrés dans le champ prenom s'affiche bien. Cela pose problème, car dans la plupart des cas, une saisie composée uniquement d'espaces n'est pas considérée comme valide, même si elle contient techniquement des caractères.
-
Modifier alors la récupération des données utilisateur pour supprimer les espaces situés au début et à la fin de la chaîne.
Cela permet d'éliminer les éventuels espaces ajoutés par accident en début ou en fin de saisie et,
dans le cas d'une valeur composée uniquement d'espaces,
de la transformer en chaîne vide afin qu'elle soit considérée comme non communiquée.
- Lors de la récupération de $prenom et de $surnom, ne plus se contenter de vérifier si la donnée existe et d'utiliser une chaîne vide dans le cas contraire.
- Appliquer également trim() à la valeur récupérée afin de supprimer les espaces situés au début et à la fin de la chaîne avant les vérifications de validation.
-
Refaire maintenant le test précédent.
- Revenir au formulaire.
- Saisir à nouveau uniquement des espaces dans le champ prenom.
- Laisser le champ surnom vide.
- Soumettre le formulaire.
- Observer le nouveau comportement obtenu. Après le passage de trim(), une valeur composée uniquement d'espaces devient une chaîne vide. Le champ requis prenom devrait donc maintenant être considéré comme non rempli, et le message d'erreur personnalisé indiquant que le prénom est obligatoire devrait s'afficher.
-
Faire enfin deux tests complémentaires.
- Saisir un prénom valide entouré d'espaces au début et à la fin.
- Vérifier que la valeur reste acceptée après nettoyage.
- Saisir ensuite un surnom composé uniquement d'espaces.
- Vérifier qu'il est alors traité comme une chaîne vide. Comme il s'agit d'un champ facultatif, rien ne devrait s'afficher : ni message d'erreur personnalisé, ni message du type Surnom saisi :.
Étape 04
Jusqu'ici, le formulaire utilisait la méthode GET. Cette méthode est pratique dans certains cas, par exemple pour une recherche, une pagination ou un tri, car les données apparaissent dans l'URL. Dans de nombreux formulaires, on préfère toutefois utiliser POST. Dans cette étape, l'objectif est donc d'adapter le code pour qu'il ne traite plus des données envoyées en GET, mais des données envoyées en POST.
- Dans index.php, modifier la valeur de l'attribut method du formulaire pour utiliser post au lieu de get.
- Revenir sur le formulaire, saisir une valeur dans les deux champs puis soumettre.
- Observer le comportement obtenu. Rien ne s'affiche comme auparavant, car le formulaire envoie maintenant les données en POST, alors que le script de traitement attend encore une requête GET et lit toujours les valeurs dans $_GET.
-
Dans traitement-form.php, modifier alors tout le code concerné pour l'adapter à
POST.
- Dans la condition principale, remplacer la vérification de la méthode de requête dans $_SERVER pour tester POST au lieu de GET.
- Ici, il n'est plus nécessaire de vérifier en plus si $_POST est vide. Cette vérification était utile avec GET, car une page est affichée en GET même sans soumission de formulaire ni paramètre dans l'URL. Avec POST, le test sur précédent permet déjà de savoir que le script traite une soumission envoyée avec cette méthode.
- Il faut aussi remplacer les parties du traitement qui utilisent $_GET avec les clés prenom et surnom par leurs équivalents dans $_POST.
-
Refaire enfin le test complet.
- Revenir au formulaire.
- Saisir une valeur dans les deux champs.
- Soumettre le formulaire.
- Vérifier que tout fonctionne à nouveau comme avant.
Étape 05
Le formulaire possède déjà une première couche de vérification grâce au HTML. Dans cette étape, l'objectif est de constater que cette protection côté client ne suffit pas, puis d'ajouter une validation côté serveur.
-
Dans index.php, ajouter des contraintes de longueur sur les deux champs.
- Ajouter un minlength de 2 et un maxlength de 20 au champ prenom.
- Ajouter également un minlength de 2 et un maxlength de 20 au champ surnom.
-
Tester d'abord le comportement du navigateur.
- Revenir sur le formulaire.
- Tenter d'entrer des valeurs qui ne respectent pas les longueurs imposées.
- Soumettre le formulaire.
- Observer que le navigateur bloque l'envoi du formulaire.
-
Contourner ensuite ces vérifications HTML.
- Ouvrir les outils de développement du navigateur.
- Supprimer dans l'inspecteur les attributs minlength et maxlength des deux champs.
- Conserver les mêmes valeurs invalides.
- Soumettre à nouveau le formulaire.
- Observer le résultat obtenu. Cette fois, le formulaire peut être envoyé alors que les règles de longueur ne sont pas respectées. La requête passe donc bien jusqu'au serveur, qui reçoit et affiche malgré tout les valeurs saisies. Cela montre qu'une vérification réalisée uniquement côté client peut être contournée.
- Compléter alors la validation côté serveur du champ requis prenom en ajoutant un test avec mb_strlen() pour vérifier que la longueur de la valeur reçue respecte bien les limites fixées. Ce nouveau test doit être ajouté après celui qui vérifie que le champ n'est pas vide et avant l'affichage normal de la valeur lorsque tout est correct. Si la longueur n'est pas respectée, afficher un message d'erreur adapté.
- Compléter alors la validation côté serveur du champ facultatif surnom en ajoutant un test avec mb_strlen() pour vérifier que la longueur de la valeur reçue respecte bien les limites fixées. Comme ce champ est facultatif, ce test ne doit pas être effectué systématiquement, mais uniquement lorsqu'une valeur a été saisie. Il doit donc être ajouté dans le bloc d'instructions qui traite le cas où le champ a été rempli. Si la longueur n'est pas respectée, afficher un message d'erreur adapté. Sinon, afficher la phrase prévue avec l'entrée utilisateur.
-
Refaire les tests après ces modifications.
- Soumettre une valeur trop courte ou trop longue dans les deux champs en supprimant les attributs HTML minlength et maxlength via l'inspecteur du navigateur.
- Vérifier que les messages d'erreur personnalisés s'affichent côté serveur.
- Soumettre le formulaire avec un prénom et un surnom valide.
- Vérifier que les deux entrées utilisateur sont bien affichées.
- Soumettre enfin un prénom valide en laissant le champ surnom vide.
- Vérifier que l'entrée utilisateur prénom s'affiche bien et que le fait que surnom soit vide ne provoque toujours pas d'erreur puisqu'il est facultatif.
Étape 06
Jusqu'ici, les vérifications côté serveur portaient surtout sur la présence d'une valeur et sur sa longueur. Dans cette étape, l'objectif est de vérifier également qu'une donnée respecte un format précis. Pour cela, vous allez ajouter un champ email obligatoire et contrôler côté serveur que la valeur reçue correspond bien à une adresse e-mail valide.
-
Dans index.php, ajouter un nouveau champ obligatoire pour l'adresse e-mail.
- Ajouter un libellé et un champ de formulaire pour l'e-mail.
- Ce champ doit utiliser le type email.
- Lui donner le nom email.
- Le rendre obligatoire avec l'attribut required.
-
Dans traitement-form.php, ajouter le traitement de cette nouvelle entrée utilisateur.
- Créer une variable $email.
- Lors de sa récupération, vérifier si la donnée existe et utiliser une chaîne vide dans le cas contraire.
- Appliquer également trim() à la valeur récupérée.
-
Ajouter ensuite une première validation côté serveur pour ce champ requis.
- Si $email est vide, afficher un message d'erreur indiquant que l'e-mail est obligatoire.
- Sinon, afficher le message E-mail saisi : suivi de l'e-mail entré.
-
Faire un premier test avec une adresse e-mail valide.
- Revenir au formulaire.
- Remplir les champs avec des valeurs valides, y compris une adresse e-mail correcte.
- Soumettre le formulaire.
- Vérifier que le message E-mail saisi : suivi de l'adresse entrée s'affiche correctement.
-
Faire ensuite un test avec une adresse e-mail invalide.
- Revenir au formulaire.
- Entrer une valeur qui ne correspond pas à une adresse e-mail valide.
- Constater que le navigateur empêche normalement la soumission, car le champ utilise le type email.
-
Contourner maintenant cette protection côté client.
- Ouvrir les outils de développement du navigateur.
- Modifier dans l'inspecteur le type du champ pour remplacer email par text, ou soumettre le formulaire par un autre moyen avec une adresse invalide.
- Le message E-mail saisi : suivi de l'adresse entrée s'affiche alors qu'elle ne devrait pas pusiqu'il s'agit d'elle est invalide.
-
Ajouter alors une vérification côté serveur du format de l'e-mail.
- Conserver d'abord le test qui vérifie si $email est vide, car le champ reste obligatoire.
- Si une valeur a bien été saisie, ajouter ensuite un test utilisant un filtre de validation d'adresse e-mail avec la fonction prédéfinie filter_var(). Lui passer $email en premier argument et le filtre FILTER_VALIDATE_EMAIL en second argument.
- Si l'adresse n'est pas valide, FILTER_VALIDATE_EMAIL retourne false. Dans ce cas, afficher un message d'erreur personnalisé indiquant que l'e-mail communiqué n'est pas une adresse e-mail valide.
- Sinon, conserver l'affichage normal du message E-mail saisi : suivi de l'adresse entrée.
-
Réaliser les tests suivants :
- Revenir au formulaire et entrer une adresse e-mail invalide en contournant à nouveau la vérification HTML via l'inspecteur du navigateur. Vérifier que le nouveau message d'erreur personnalisé indique bien qu'il s'agit d'une adresse e-mail invalide.
-
Vérifier ensuite qu'aucun des cas précédents n'a été cassé entre-temps :
- Supprimer l'attribut required du champ email, puis soumettre le formulaire en laissant ce champ vide. Vérifier que le message indique bien que ce champ est requis.
- Entrer enfin une adresse e-mail correcte afin de vérifier que le message E-mail saisi : suivi de l'adresse entrée s'affiche bien.
Étape 07
Jusqu'ici, les messages étaient affichés directement dans le fichier de traitement. Cette approche permet de comprendre la logique de validation, mais elle ne place pas encore les retours utilisateur au bon endroit. Dans cette étape, l'objectif est d'afficher chaque message d'erreur sous le champ concerné ainsi qu'un message global indiquant si le formulaire a été envoyé correctement ou non.
-
Dans traitement-form.php, préparer d'abord deux nouvelles variables dans le bloc d'instructions principal, c'est-à-dire à l'intérieur du test qui vérifie qu'une requête de soumission a bien été reçue.
- Créer un tableau vide $erreurs destiné à stocker les messages d'erreur liés aux différents champs du formulaire.
- Créer également une variable $messageGlobal contenant une chaîne vide destinée à contenir un message global.
- Ensuite plutôt que d'afficher directement les erreurs avec echo, enregistrer l'erreur dans le tableau $erreurs à la clé correspondant au nom du champ (ex.: $erreurs['prenom']).
-
À la fin du bloc de vérification, définir le message global.
- Si le tableau d'erreurs $erreurs est vide, cela signifie qu'aucune erreur n'a été rencontrée.
- Dans ce cas, enregistrer dans la variable $messageGlobal indiquant que le formulaire a bien été envoyé.
- Sinon, y enregistrer un message indiquant que le formulaire contient des erreurs.
-
Revenir ensuite dans index.php pour afficher ces messages au bon endroit.
- Sous chaque champ, ajouter un couple de balise ouvrante/fermante div qui servira de conteneur pour accueillir le message d'erreur si celui-ci existe.
- Sous le bouton de soumission, ajouter un couple de balise ouvrante/fermante div qui servira à afficher le message global de validation si une tentative de soumission a été réalisée, c'est-à-dire si $messageGlobal existe.
-
Modifier ensuite le comportement après traitement pour revenir automatiquement sur la page du formulaire.
- Ajouter une redirection vers la racine du site à l'aide de header().
- L'objectif est que, après soumission, l'utilisateur revienne sur la page du formulaire.
-
Réaliser le test suivant une fois la redirection mise en place.
- Soumettre le formulaire avec des valeurs valides.
- Vérifier que le navigateur réaffiche bien la page du formulaire après soumission.
- Qu'il y ait eu des erreurs ou non, aucun message n'apparaît, pas même le message global de validation. C'est normal, car la redirection recharge la page à travers une nouvelle requête. Les variables créées dans le fichier de traitement lors de la requête précédente ne sont donc plus disponibles. Il existe des techniques permettant de conserver temporairement des informations entre deux requêtes, mais elles seront abordées plus tard. Pour l'instant, nous allons utiliser une autre approche en incluant directement le fichier de traitement avant l'affichage du formulaire.
-
Mettre alors en place une solution plus simple pour cette étape.
- Dans le fichier index.php, supprimer l'attribut action du formulaire pour que celui-ci envoie les données vers la page courante.
- Tout en haut du fichier index.php, avant la première ligne de code HTML, inclure le fichier traitement-form.php.
- Ainsi, le traitement sera exécuté avant l'affichage du formulaire, dans la même requête.
- Les messages préparés dans le fichier de traitement pourront alors être accessibles par le formulaire pour y être affichés aux endroits désirés.
- Dans traitement-form.php, n'oubliez surtout pas de supprimer la redirection ajoutée précédemment. Si elle reste présente, la page du formulaire inclura le fichier de traitement, qui redirigera à nouveau vers cette même page, avec un risque de boucle de redirection.
-
Refaire enfin les tests.
- Soumettre le formulaire avec des données valides.
- Vérifier que le message global de réussite s'affiche bien sur la page du formulaire.
- Soumettre ensuite le formulaire avec des données invalides (il faudra trafiquer les attributs HTML des champs via l'inspecteur du navigateur).
- Vérifier que le message global d'erreur ainsi que les messages placés sous les champs concernés apparaissent correctement.
Étape 08
Jusqu'ici, lorsque le formulaire contient des erreurs, les messages s'affichent bien au bon endroit. En revanche, les valeurs déjà saisies par l'utilisateur disparaissent au moment où la page est réaffichée. Cela nuit au confort d'utilisation, car l'utilisateur doit retaper toutes les informations. Dans cette étape, l'objectif est donc de réafficher automatiquement les anciennes valeurs du formulaire lorsqu'une soumission contient des erreurs.
-
Dans traitement-form.php, préparer une nouvelle variable dans le bloc d'instructions principal, c'est-à-dire à l'intérieur du test qui vérifie qu'une requête POST a bien été reçue.
- Créer un tableau vide nommé $anciennesValeurs.
- Ce tableau servira à conserver les valeurs déjà saisies par l'utilisateur lorsque le formulaire contient des erreurs.
-
Adapter ensuite le bloc qui définit le message global en fin de traitement.
- Si le tableau $erreurs est vide, conserver le fonctionnement actuel en indiquant que le formulaire a bien été envoyé.
- En revanche, si le formulaire contient des erreurs, conserver aussi dans $anciennesValeurs les valeurs déjà présentes dans $prenom, $surnom et $email. Le principe est le même que pour le tableau $erreurs, chaque valeur est rangée à une clé portant le nom du champ auquel elle correspond.
- L'objectif est de pouvoir réinjecter ces valeurs dans les champs au moment du nouveau rendu de la page.
-
Revenir ensuite dans index.php pour adapter le formulaire.
- Ajouter un attribut value au champ prenom.
- Faire en sorte que cet attribut affiche la valeur conservée dans $anciennesValeurs['prenom'] si elle existe, sinon une chaîne vide.
- Faire le même travail pour les champs surnom et email avec leur clé correspondante.
-
Réaliser un premier test pour vérifier le réaffichage des anciennes valeurs lorsqu'une erreur est présente.
- Remplir correctement deux des champs du formulaire.
- Entrer dans le troisième champ une valeur invalide ne respectant pas les règles prévues.
- Soumettre le formulaire.
- Vérifier que les valeurs saisies sont bien réaffichées dans leurs champs respectifs.
-
Réaliser ensuite un second test avec uniquement des valeurs valides.
- Remplir correctement tous les champs du formulaire.
- Soumettre le formulaire.
- Vérifier que le message global indique bien que le formulaire a été envoyé sans erreur.
- Vérifier également que les champs du formulaire ne réaffichent pas les anciennes valeurs après une soumission correcte.
Étape 09
Jusqu'ici, les anciennes valeurs saisies par l'utilisateur sont bien réaffichées dans les champs lorsque le formulaire contient des erreurs. Il reste toutefois un problème de sécurité. Si une valeur malveillante est réinjectée telle quelle dans le HTML, le navigateur peut l'interpréter comme du code. Dans cette étape, l'objectif est donc de sécuriser le réaffichage des entrées utilisateur afin d'éviter qu'un script injecté ne soit exécuté.
-
Réaliser d'abord un test d'injection dans l'un des champs du formulaire.
- Revenir au formulaire.
- Dans l'un des champs texte, saisir le contenu suivant : "><script>alert('vous avez un virus appelez microsoft au numéro suivant 132456789')</script>
- Compléter le reste du formulaire de manière à provoquer une erreur sur un autre champ, afin que les anciennes valeurs soient réaffichées.
- Soumettre ensuite le formulaire.
-
Observer le comportement obtenu.
- La balise <script> est interprétée par le navigateur.
- Le script est donc exécuté au moment du réaffichage de la valeur injectée.
- Cela montre qu'il ne faut jamais réafficher directement une entrée utilisateur dans le HTML sans la sécuriser au préalable.
-
Remettre ce problème dans son contexte.
- Dans cet exercice, l'utilisateur ne voit ici que ses propres entrées.
- Le risque immédiat semble donc limité.
- Il s'agit néanmoins d'une très mauvaise habitude à prendre.
- Si ces données étaient un jour stockées puis réaffichées sur une page consultée par d'autres utilisateurs, le script injecté pourrait alors s'exécuter dans leur navigateur.
-
Sécuriser alors le réaffichage des anciennes valeurs dans index.php.
- Repérer les attributs value ajoutés à l'étape précédente pour réafficher les anciennes valeurs.
- Ne plus y réinjecter directement les contenus provenant de $anciennesValeurs.
- Utiliser à la place la fonction prédéfinie htmlspecialchars().
- Lui passer la valeur à afficher en premier argument.
- Utiliser ensuite les réglages ENT_QUOTES | ENT_SUBSTITUTE ainsi que l'encodage 'UTF-8'.
-
Rappel du rôle de ces réglages.
- ENT_QUOTES permet d'échapper à la fois les guillemets doubles et les guillemets simples.
- ENT_SUBSTITUTE permet de remplacer proprement d'éventuelles séquences de caractères invalides au lieu de produire un résultat incorrect.
- L'encodage UTF-8 doit être précisé explicitement pour que l'échappement soit réalisé dans le bon encodage.
-
Refaire maintenant le même test d'injection.
- Saisir à nouveau le script malveillant dans un champ.
- Provoquer une erreur sur un autre champ afin que les anciennes valeurs soient à nouveau réaffichées.
- Soumettre le formulaire.
-
Observer le nouveau comportement obtenu.
- Le script ne devrait plus être exécuté.
- La valeur injectée devrait être affichée comme un simple texte.
Étape 10
Jusqu'ici, les anciennes valeurs étaient réaffichées dans des champs de type input. Dans ce cas, la valeur se place dans l'attribut value. Avec une balise textarea, le principe est différent. Le contenu ne se place pas dans un attribut value, mais entre la balise ouvrante et la balise fermante. Dans cette étape, l'objectif est donc d'ajouter un champ message requis, de le valider côté serveur et de réafficher son contenu correctement en cas d'erreur.
-
Dans index.php, ajouter un nouveau champ requis message.
- Ajouter un libellé et une balise textarea pour ce champ.
- Lui donner le nom message.
- Le rendre obligatoire avec l'attribut required.
- Ajouter un minlength de 10 et un maxlength de 500.
- Sous ce champ, ajouter un conteneur div destiné à accueillir un éventuel message d'erreur personnalisé.
-
Dans traitement-form.php, ajouter le traitement de cette nouvelle entrée utilisateur.
- Créer une variable $message.
- Si la donnée n'a pas été transmise, lui attribuer une chaîne vide.
- Appliquer également trim() afin de supprimer les espaces placés au début et à la fin de la chaîne avant les vérifications.
-
Ajouter ensuite la validation côté serveur du champ requis message.
- Si $message est vide, enregistrer un message d'erreur personnalisé indiquant que le message est obligatoire.
- Sinon, vérifier avec mb_strlen() que sa longueur respecte bien la plage autorisée.
- Si le message contient moins de 10 caractères, enregistrer un message d'erreur adapté.
- Si le message contient plus de 500 caractères, enregistrer également un message d'erreur adapté.
- Si le formulaire contient des erreurs, penser aussi à conserver la valeur de $message dans le tableau $anciennesValeurs avec la clé message, selon le même principe que pour les autres champs.
-
Revenir ensuite dans index.php pour mettre en place le réaffichage de l'ancienne valeur.
- Contrairement à une balise input, ne pas utiliser d'attribut value.
- Réafficher l'ancienne valeur de message entre la balise ouvrante et la balise fermante de textarea.
- Comme pour les autres champs, sécuriser ce réaffichage avec htmlspecialchars() et les réglages recommandés déjà utilisés précédemment.
-
Réaliser un premier test avec un message vide en contournant la vérification HTML.
- Revenir au formulaire.
- Ouvrir les outils de développement du navigateur.
- Supprimer l'attribut required du champ message.
- Soumettre ensuite le formulaire en laissant ce champ vide.
- Vérifier que le message d'erreur personnalisé indiquant que ce champ est obligatoire s'affiche bien.
-
Réaliser ensuite un deuxième test avec un message trop court.
- Revenir au formulaire.
- Supprimer l'attribut minlength du champ message via l'inspecteur du navigateur.
- Entrer un message contenant moins de 10 caractères.
- Soumettre le formulaire.
- Vérifier que le message d'erreur personnalisé indiquant que le texte est trop court s'affiche bien.
- Vérifier également que le contenu saisi est bien réaffiché dans la balise textarea.
-
Réaliser enfin un troisième test avec un message trop long.
- Revenir au formulaire.
- Supprimer l'attribut maxlength du champ message via l'inspecteur du navigateur.
- Entrer un message dépassant 500 caractères.
- Soumettre le formulaire.
- Vérifier que le message d'erreur personnalisé indiquant que le texte est trop long s'affiche bien.
- Vérifier également que le contenu saisi est bien réaffiché dans la balise textarea.
Exercice 02
Dans cet exercice, vous allez poursuivre le travail sur le traitement des formulaires en PHP, cette fois avec d'autres types de champs que les simples champs texte. L'objectif est de découvrir comment récupérer, valider et réafficher correctement les valeurs provenant de champs de type radio, select (sélection unique et multiple) et checkbox (case unique et sélection multiple).
Le contexte choisi est celui d'un formulaire d'inscription à une formation web. Ce thème permet de donner un rôle clair à chaque type de champ et d'éviter un assemblage artificiel de contrôles sans lien entre eux.
Structure
Créer un dossier nommé Exo-comprendre-et-traiter-donnees-formulaire-02. Toutes les étapes de cet exercice seront réalisées dans ce même dossier de travail.
Débuter l'exercice avec la structure suivante :
📁 Exo-comprendre-et-traiter-donnees-formulaire-02/
├── 📁 traitements/
│ └── traitement-form.php
└── 📄 index.php
Étape 01
Dans cette première étape, vous allez mettre en place la structure générale du formulaire et traiter un premier champ de type select simple. Cette base servira ensuite à accueillir les autres types de champs.
-
Dans index.php, préparer la structure générale du formulaire.
- Tout en haut du fichier, avant la première ligne de code HTML, inclure le fichier traitement-form.php.
- Créer un formulaire envoyé en POST.
- Ne pas ajouter d'attribut action, afin que le formulaire soit soumis vers la page courante.
- Ajouter un bouton de soumission avec le texte Envoyer.
- Sous ce bouton, prévoir un conteneur div destiné à afficher un éventuel message global.
-
Ajouter ensuite un champ
select
simple nommé
pays.
- Ce champ servira à choisir le pays de résidence du participant.
- Ajouter une option vide en première position afin que l'utilisateur doive réellement faire un choix.
- Ajouter ensuite plusieurs pays, par exemple belgique, france, canada et suisse.
- Rendre ce champ obligatoire.
- Ajouter sous ce champ un conteneur div destiné à afficher son éventuel message d'erreur.
-
Dans traitement-form.php, créer le bloc principal de traitement.
- Vérifier d'abord qu'une requête POST a bien été reçue.
- À l'intérieur de ce bloc, préparer un tableau vide $erreurs, une chaîne vide $messageGlobal et un tableau vide $anciennesValeurs.
- Ces variables serviront respectivement à stocker les erreurs, le message global et les anciennes valeurs du formulaire si au moins un champ est invalide.
-
Ajouter ensuite le traitement du champ
pays.
- Récupérer sa valeur dans une variable $pays.
- Si la clé n'existe pas, utiliser une chaîne vide à la place.
- Préparer également un tableau contenant la liste des pays autorisés.
- Si aucun pays n'a été choisi, enregistrer un message d'erreur indiquant qu'un choix est obligatoire.
- Sinon, vérifier que la valeur reçue fait bien partie des pays autorisés.
- Si ce n'est pas le cas, enregistrer un message d'erreur indiquant que la valeur reçue est invalide.
-
Définir ensuite le message global.
- Si le tableau $erreurs est vide, le message global doit indiquer que le formulaire a bien été envoyé.
- Sinon, le message global doit indiquer que le formulaire contient des erreurs.
- Dans ce second cas, conserver aussi la valeur de $pays dans le tableau $anciennesValeurs à la clé pays.
-
Revenir ensuite dans index.php pour mettre en place le réaffichage et l'affichage des messages.
- Dans la balise option correspondant à chaque pays, ajouter une condition qui compare la valeur de l'option avec celle conservée dans $anciennesValeurs['pays'].
- Si les deux valeurs correspondent, ajouter l'attribut selected afin que l'option précédemment choisie soit automatiquement resélectionnée après une erreur.
- Sous le champ, dans le conteneur div prévu pour les erreurs, afficher le message contenu dans $erreurs['pays'] si cette clé existe.
- Sous le bouton de soumission, dans le conteneur prévu pour le message global, afficher la valeur de $messageGlobal si cette variable existe et n'est pas vide.
-
Réaliser les premiers tests.
- Soumettre le formulaire avec un pays valide et vérifier que le message global de réussite s'affiche.
- Soumettre ensuite le formulaire sans choisir de pays en contournant si nécessaire la vérification HTML via l'inspecteur du navigateur.
- Vérifier que le message d'erreur signalant qu'un pays doit être sélectionné s'affiche bien.
- Modifier enfin manuellement la valeur d'une option dans l'inspecteur du navigateur, puis soumettre à nouveau le formulaire pour vérifier que la validation côté serveur rejette une valeur qui ne fait pas partie de la liste autorisée.
- Il n'est pas encore possible de vérifier ici de manière vraiment parlante le mécanisme de réaffichage ou de resélection après erreur. En effet, pour l'instant, le formulaire ne contient qu'un seul champ de sélection. Si aucun pays n'est choisi, il n'y a tout simplement aucune valeur à resélectionner. Et si une valeur invalide est envoyée, par exemple en modifiant manuellement le HTML, elle ne correspond à aucune option réellement présente dans la liste, donc rien ne peut être resélectionné visuellement dans le formulaire. Ces tests seront donc réalisés à la fin de l'étape 02, lorsque le formulaire contiendra un champ supplémentaire permettant de mieux observer la conservation des valeurs déjà saisies.
Étape 02
Vous allez maintenant ajouter un groupe de boutons radio. Contrairement au select, l'utilisateur voit directement tous les choix disponibles. En revanche, la logique côté serveur reste proche une seule valeur est attendue, et elle doit appartenir à une liste de valeurs autorisées.
-
Dans index.php, ajouter un groupe de boutons
radio
nommé
niveau.
- Ce champ servira à indiquer le niveau actuel du participant.
- Ajouter par exemple les choix debutant, intermediaire et avance.
- Rendre ce choix obligatoire.
- Ajouter sous le groupe un conteneur div destiné à afficher son éventuel message d'erreur.
-
Dans traitement-form.php, ajouter le traitement du champ
niveau.
- Récupérer la valeur dans une variable $niveau.
- Si la clé n'existe pas, utiliser une chaîne vide.
- Préparer un tableau des niveaux autorisés.
- Si aucun niveau n'a été choisi, enregistrer un message d'erreur adapté.
- Sinon, vérifier que la valeur reçue fait bien partie de la liste autorisée.
- Si ce n'est pas le cas, enregistrer un message d'erreur indiquant que la valeur envoyée est invalide.
-
Adapter ensuite la conservation des anciennes valeurs.
- Si le formulaire contient au moins une erreur, conserver aussi $niveau dans $anciennesValeurs à la clé niveau.
- Le principe reste le même que pour pays.
-
Revenir ensuite dans index.php pour mettre en place le réaffichage du choix et l'affichage du message d'erreur.
- Dans chaque balise input de type radio du champ niveau, vérifier si la valeur de l'attribut value correspond à celle conservée dans $anciennesValeurs['niveau'].
- Si c'est le cas, ajouter l'attribut checked afin que le bon bouton radio soit automatiquement recoché lors du nouveau rendu de la page.
- Sous le groupe de boutons radio, dans le conteneur div prévu à cet effet, afficher le message d'erreur correspondant si la clé niveau existe dans le tableau $erreurs.
-
Réaliser les tests suivants.
- Soumettre d'abord le formulaire avec un pays valide et un niveau valide afin de vérifier que rien n'a été cassé et que le message global de réussite s'affiche toujours correctement.
- Revenir ensuite au formulaire, puis supprimer dans l'inspecteur du navigateur l'attribut required du groupe niveau afin de pouvoir soumettre le formulaire sans choisir de niveau.
- Soumettre le formulaire sans sélectionner de niveau et vérifier que le message d'erreur personnalisé indiquant que ce champ est requis s'affiche bien.
- Vérifier également que la sélection précédente du champ pays est bien conservée après cette erreur.
- Modifier ensuite manuellement, via l'inspecteur du navigateur, la valeur d'un des boutons radio du champ niveau pour lui donner une valeur qui ne fait pas partie de la liste autorisée, puis soumettre à nouveau le formulaire.
- Vérifier que le message d'erreur personnalisé indiquant que la valeur envoyée pour le niveau est invalide s'affiche bien.
- Enfin, provoquer une erreur sur le champ pays tout en sélectionnant un niveau valide, puis vérifier que le niveau choisi est bien recoché lors du nouveau rendu de la page.
Étape 03
Vous allez maintenant ajouter une case à cocher unique. Ce type de champ est souvent utilisé pour confirmer une condition, par exemple l'acceptation d'un règlement. Son comportement a une particularité importante lorsqu'elle n'est pas cochée, sa clé n'est pas envoyée dans $_POST.
-
Dans index.php, ajouter une case à cocher unique nommée
reglement.
- Cette case servira à confirmer l'acceptation du règlement de la formation.
- Lui donner la valeur oui.
- Rendre ce choix obligatoire (required).
- Ajouter sous cette case un conteneur div destiné à afficher un éventuel message d'erreur.
-
Dans traitement-form.php, ajouter le traitement de cette nouvelle entrée.
- Récupérer la valeur dans une variable $reglement.
- Si la clé n'existe pas, utiliser une chaîne vide.
- Vérifier ensuite que la valeur reçue est exactement oui.
- Si ce n'est pas le cas, enregistrer un message d'erreur indiquant que le règlement doit être accepté.
- Si le formulaire contient au moins une erreur, conserver aussi la valeur de $reglement dans $anciennesValeurs à la clé reglement.
-
Revenir ensuite dans index.php pour mettre en place le réaffichage de la case à cocher et l'affichage du message d'erreur.
- Dans la balise input de type checkbox correspondant au champ reglement, vérifier si la valeur conservée dans $anciennesValeurs['reglement'] est bien égale à oui.
- Si c'est le cas, ajouter l'attribut checked afin que la case soit automatiquement recochée lors du nouveau rendu de la page.
- Sous cette case, dans le conteneur div prévu à cet effet, afficher le message d'erreur correspondant si la clé reglement existe dans le tableau $erreurs.
-
Réaliser les tests suivants.
- Soumettre le formulaire en laissant cette case décochée, en contournant la vérification HTML (retirer required via l'inspecteur du navigateur).
- Vérifier que le message d'erreur personnalisé indiquant que le règlement doit être accepté s'affiche bien.
- Modifier ensuite manuellement la valeur de la case (value) dans l'inspecteur du navigateur, puis soumettre à nouveau le formulaire.
- Vérifier que le traitement refuse toute valeur différente de oui.
Étape 04
Vous allez maintenant ajouter des cases à cocher multiples. Cette fois, plusieurs valeurs peuvent être envoyées pour un même champ. PHP ne reçoit donc plus une simple chaîne, mais un tableau de valeurs. Ces cases correspondent aux créneaux horaires où l'utilisateur est disponible pour suivre les formations.
-
Dans index.php, ajouter un groupe de cases à cocher multiples nommé
creneaux[].
- Ce champ servira à indiquer les créneaux qui intéressent le participant.
- Ajouter les valeurs 9h00-12h45, 13h15-17h15 et 18h00-21h45
- Prévoir un conteneur div sous le groupe pour afficher un éventuel message d'erreur.
-
Dans traitement-form.php, ajouter le traitement correspondant.
- Si la clé creneaux existe et qu'il s'agit bien d'un tableau, stocker sa valeur dans $creneaux. Sinon, lui assigner un tableau vide.
- Préparer également un tableau contenant les creneaux autorisés.
- Si aucun creneau n'a été sélectionné, enregistrer un message d'erreur personnalisé indiquant qu'il faut choisir au moins un creneau.
- Sinon, parcourir toutes les valeurs reçues et vérifier qu'elles appartiennent bien à la liste autorisée.
- Si au moins une valeur est invalide, interrompre la boucle et enregistrer un message d'erreur indiquant qu'au moins un des creneaux sélectionnés n'existe pas.
- Si le formulaire contient au moins une erreur, conserver aussi le tableau $creneaux dans $anciennesValeurs à la clé creneaux.
-
Revenir ensuite dans index.php.
- Repérer le code HTML correspondant aux cases à cocher des creneaux.
- Pour chaque case, vérifier si sa valeur est présente dans $anciennesValeurs['creneaux'].
- Si c'est le cas, ajouter l'attribut checked afin que la case soit recochée lors du réaffichage du formulaire.
- Sous le groupe de cases à cocher, afficher le message d'erreur associé si une erreur existe pour ce champ.
-
Réaliser les tests suivants.
- Soumettre le formulaire sans cocher de creneau et vérifier que l'erreur indiquant qu'il faut sélectionner au moins un creneau s'affiche bien.
- Sélectionner ensuite plusieurs creneaux valides et vérifier qu'ils restent cochés si une erreur est présente sur un autre champ du formulaire.
- Dans l'inspecteur du navigateur, modifier manuellement la valeur d'une des cases à cocher, puis soumettre le formulaire avec cette sélection invalide pour vérifier que la validation côté serveur la détecte et affiche le message approprié.
Étape 05
Vous allez maintenant ajouter un select multiple. Comme avec des cases à cocher multiples, ce type de champ permet d'envoyer plusieurs valeurs via un tableau. Ces options correspondent aux langages que l'utilisateur souhaite apprendre.
-
Dans index.php, ajouter un champ
select
multiple nommé
langages[].
- Ce champ servira à indiquer les langages que le participant souhaite approfondir.
- Ajouter l'attribut HTML multiple pour permettre la sélection multiple.
- Ajouter par exemple les options html, css, javascript, php et sql.
- Rendre ce choix obligatoire (required).
- Ajouter sous ce champ un conteneur div destiné à afficher un éventuel message d'erreur.
-
Dans traitement-form.php, ajouter le traitement correspondant.
- Si la clé langages existe et qu'il s'agit bien d'un tableau, stocker sa valeur dans $langages. Sinon, lui assigner un tableau vide.
- Préparer également un tableau contenant les langages autorisés.
- Si aucun langage n'a été sélectionné, enregistrer un message d'erreur personnalisé indiquant qu'il faut choisir au moins un langage.
- Sinon, parcourir toutes les valeurs reçues et vérifier qu'elles appartiennent bien à la liste autorisée.
- Si au moins une valeur est invalide, interrompre la boucle et enregistrer un message d'erreur indiquant qu'au moins un des langages sélectionnés n'existe pas.
- Si le formulaire contient au moins une erreur, conserver aussi le tableau $langages dans $anciennesValeurs à la clé langages.
-
Revenir ensuite dans index.php pour mettre en place la resélection des options et l'affichage du message d'erreur.
- Dans chaque balise option du champ langages[], vérifier si la valeur de cette option se trouve dans le tableau $anciennesValeurs['langages'].
- Si c'est le cas, ajouter l'attribut selected afin que cette option reste sélectionnée lors du nouveau rendu de la page.
- Sous le champ, dans le conteneur div prévu à cet effet, afficher le message d'erreur correspondant si la clé langages existe dans le tableau $erreurs.
-
Réaliser les tests suivants.
- Contourner la vérification HTML du champ requis en supprimant l'attribut required à l'aide de l'inspecteur du navigateur.
- Soumettre le formulaire sans sélectionner de langage et vérifier que l'erreur indiquant qu'il faut sélectionner au moins un langage s'affiche bien.
- Sélectionner ensuite plusieurs langages valides et vérifier qu'ils restent bien sélectionnés si une erreur est présente sur un autre champ.
- Dans l'inspecteur du navigateur, modifier manuellement la valeur d'une des options, puis soumettre le formulaire avec cette sélection invalide pour vérifier que la validation côté serveur la détecte et affiche le message approprié.
Exercice : Projet Progressif 02
Dans cette deuxième partie du projet progressif, vous allez enrichir la page contact.php en y ajoutant un vrai formulaire traité côté serveur. L'objectif est de mettre en pratique les notions vues dans ce chapitre dans un contexte plus réaliste, directement intégré au site commencé dans la partie précédente.
Attendu
À la fin de cet exercice, la page Contact doit contenir un formulaire fonctionnel, validé côté serveur, capable d'afficher un message global, des messages d'erreur sous les champs concernés et de réafficher les anciennes valeurs en cas d'erreur. La page doit continuer à utiliser le modèle commun du projet progressif 01 avec son en-tête, son pied de page et sa feuille de style partagée.
Structure
Poursuivre le projet commencé dans la partie précédente en ajoutant le dossier et le fichier suivants :
📁 projet-progressif
├── 📁 assets/
│ └── 📁 css/
│ └── 📄 style.css
├── 📁 config/
│ └── 📄 config.php
├── 📁 templates/
│ ├── 📄 header.php
│ └── 📄 footer.php
├── 🆕📁 traitements/
│ └── 🆕📄 traitement-contact.php
├── 📄 index.php
└── 📄 contact.php
Instructions
- Dans contact.php, ajouter un formulaire envoyé en POST. Ne pas ajouter d'attribut action, afin que le formulaire soit soumis vers la page courante.
-
Ajouter les champs suivants :
-
nom :
- champ requis (attribut HTML required),
- minimum 2 caractères (attribut HTML minlength),
- maximum 255 caractères (attribut HTML maxlength).
-
prénom :
- champ facultatif,
- minimum 2 caractères,
- maximum 255 caractères.
-
email :
- champ requis,
- de type email.
-
message :
- champ requis,
- minimum 10 caractères,
- maximum 3000 caractères.
-
nom :
- Sous chacun des champs, ajouter un conteneur div qui servira plus tard à afficher le message d'erreur du champ concerné.
- Sous le bouton d'envoi, ajouter un autre conteneur div qui servira à afficher le message global de validation.
-
Initialiser les variables de travail suivantes :
- $erreurs avec un tableau vide
- $messageGlobal avec une chaîne vide
- $anciennesValeurs avec un tableau vide
- Vérifier ensuite si la page reçoit une requête POST. Si c'est le cas, exécuter le traitement du formulaire dans ce bloc d'instructions.
- Dans ce bloc, récupérer la valeur de chaque champ. Si une clé n'existe pas, utiliser une chaîne vide à la place. Nettoyer ensuite les valeurs textuelles avec trim().
-
Valider ensuite chaque champ en fonction des règles définies côté HTML.
- pour les champs requis, vérifier qu'ils ne sont pas vides
- pour les contraintes de longueur, utiliser mb_strlen()
- pour l'e-mail, vérifier le format avec filter_var() et FILTER_VALIDATE_EMAIL
- Enregistrer les éventuels messages d'erreur dans le tableau $erreurs, en utilisant comme clés les noms des champs concernés.
-
À la fin du traitement, vérifier si le tableau
$erreurs
est vide.
- s'il est vide, enregistrer dans $messageGlobal un message positif indiquant que le formulaire a bien été envoyé
- sinon, enregistrer un message négatif indiquant que le formulaire contient des erreurs, puis conserver les valeurs déjà saisies dans $anciennesValeurs en utilisant, là aussi, les noms des champs comme clés
- Avant le code HTML, préparer des versions échappées des anciennes valeurs afin de faciliter la lecture du formulaire dans la page.
-
Dans le formulaire :
- réafficher les anciennes valeurs des champs input dans l'attribut value
- réafficher l'ancienne valeur du champ textarea entre sa balise ouvrante et sa balise fermante
- afficher les messages d'erreur sous les champs concernés s'ils existent
- afficher le message global sous le bouton d'envoi s'il existe
- Utiliser htmlspecialchars() avec les réglages ENT_QUOTES | ENT_SUBSTITUTE et l'encodage 'UTF-8' pour sécuriser le réaffichage des valeurs utilisateur.
- Tester une soumission entièrement valide et vérifier que le message global de réussite s'affiche correctement.
-
Tester ensuite plusieurs cas invalides en contournant si nécessaire les vérifications HTML via l'inspecteur du navigateur :
- champ requis vide
- valeur trop courte
- valeur trop longue
- adresse e-mail invalide
-
Vérifier que, lorsqu'une erreur est présente :
- les messages d'erreur s'affichent sous les bons champs
- le message global indique que le formulaire contient des erreurs
- les anciennes valeurs sont bien réaffichées
- Réaliser enfin un test d'injection en saisissant dans un champ une valeur contenant du HTML ou du JavaScript, puis vérifier que le contenu est réaffiché comme du texte et n'est pas interprété par le navigateur.
Étape 01
Construire le formulaire de contact dans la page.
Étape 02
Mettre en place le traitement complet du formulaire dans traitements/traitement-contact.php.
Étape 03
Brancher ensuite l'affichage des erreurs, du message global et le réaffichage des anciennes valeurs dans contact.php.
Étape 04
Réaliser les tests nécessaires pour vérifier que le formulaire se comporte correctement.
Exercices - Part 02
Exercice 03
Cet exercice a pour objectif de travailler la lisibilité et la maintenabilité du code PHP en appliquant un principe fondamental : ne pas répéter la même logique à plusieurs endroits. En relisant le code produit lors de l'exercice 01, vous constaterez que certaines opérations suivent exactement le même schéma selon les champs traités. L'objectif est d'identifier ces répétitions, puis de les extraire dans des fonctions réutilisables.
Il ne s'agit pas ici d'ajouter de nouvelles fonctionnalités. Le comportement du formulaire doit rester strictement identique à celui obtenu à la fin de l'exercice 01. Seule la structure interne du code change.
Consigne importante avant de commencer
Le dossier de l'exercice 01 doit être dupliqué avant toute modification. Ne pas travailler directement dans ce dossier original. Il sera réutilisé plus tard dans le cours et doit rester dans l'état exact où il se trouve à la fin de l'exercice 01.
Consigne sur l'utilisation de l'IA
Cet exercice doit être réalisé sans déléguer la logique de refactorisation à un outil d'IA. L'objectif est précisément de répondre à un problème avec sa propre réflexion, de développer une capacité à lire du code existant, à identifier ce qui se répète et à concevoir une solution pour l'éliminer. C'est cet effort de pensée qui constitue l'apprentissage.
L'IA peut en revanche être consultée ponctuellement pour des questions précises et isolées, par exemple pour retrouver la syntaxe exacte d'une fonction prédéfinie PHP, vérifier le comportement d'un opérateur ou clarifier un point de langage. Ce type d'usage est légitime et ne compromet pas l'exercice. Ce qui est interdit, c'est de lui soumettre le problème dans son ensemble et d'en recopier la réponse.
Structure
Dupliquer le dossier de l'exercice 01 et le renommer Exo-refactorisation-formulaire-03. Travailler uniquement dans cette copie.
Un troisième fichier devra être créé dans ce dossier pour y placer les fonctions. La structure finale attendue est la suivante :
📁 Exo-refactorisation-formulaire-03/
├── 📁 traitements/
│ └── 📄 traitement-form.php
└── 📄 index.php
Pistes
Avant de modifier quoi que ce soit, prendre le temps de relire l'intégralité de traitement-form.php et de index.php en se posant la question suivante : quelles lignes de code font exactement la même chose, mais appliquées à des champs différents ?
La récupération de chaque champ depuis $_POST suit toujours le même schéma. Elle vérifie si la clé existe, utilise une valeur par défaut si elle est absente, et applique systématiquement trim(). Ce schéma se répète autant de fois qu'il y a de champs. Une fonction pourrait prendre en charge cette opération en recevant le tableau source et le nom de la clé souhaitée, puis retourner la valeur nettoyée.
La vérification de longueur avec mb_strlen() est également répétée pour chaque champ soumis à une contrainte de longueur minimale et maximale. Les seules choses qui changent d'un champ à l'autre sont la valeur testée, les bornes autorisées et le message d'erreur retourné. Ces paramètres pourraient être passés en arguments.
L'appel à htmlspecialchars() avec ses drapeaux et son encodage est identique à chaque endroit où une ancienne valeur est réaffichée dans le formulaire. Une fonction enveloppant cet appel éviterait d'écrire les mêmes réglages à répétition et de risquer d'oublier un argument à l'un des endroits.
Une fois ces fonctions écrites, réfléchir à l'endroit le plus logique pour les stocker. Rien n'oblige à les placer à la racine du dossier de travail ni à les regrouper dans un fichier unique. Il est tout à fait possible de créer des sous-dossiers et de nouveaux fichiers pour les organiser. L'objectif est que la structure du projet reflète clairement ce que chaque fichier contient et que quelqu'un qui découvre le dossier comprenne immédiatement où trouver ce qu'il cherche. Se demander si cette organisation resterait lisible et cohérente si le projet grossissait et accueillait d'autres formulaires.
Vérification
Une fois la refactorisation terminée, relire le code et s'assurer que chaque opération générique n'est définie qu'à un seul endroit. Reprendre ensuite l'ensemble des tests réalisés lors de l'exercice 01 et vérifier que le comportement est rigoureusement identique :
- soumission valide,
- vides,
- valeurs trop courtes ou trop longues,
- adresse e-mail invalide,
- injection de script,
- champs facultatifs absents.
- Aucune régression ne doit apparaître.
Comment circule une soumission
Lorsqu'un utilisateur remplit un formulaire et clique sur le bouton d'envoi, plusieurs étapes se succèdent.
Le rôle du script serveur est déterminant. Ce n'est pas le navigateur qui tranche si une donnée est correcte, autorisée ou sûre. Le navigateur ne fait qu'envoyer ce qu'il possède. Le serveur, lui, doit vérifier avant d'utiliser.