Aller au contenu

Formulaires et données reçues

Jusqu’ici, les données affichées par nos pages étaient écrites par nous (dans les fichiers PHP) Cependant, une application web traite généralement aussi des données en provenance de l’utilisateur, d’autres programmes ou appareils. Dans ce chapitre, nous voyons un moyen de recevoir et traiter ce genre de données à l’aide de formulaire.

Une requête peut transporter des données par deux chemins. Le premier est l’URL elle-même : tout ce qui suit le point d’interrogation forme la chaîne de requête, v isible dans la barre d’adresse. Le second est le corps de la requête, envoyé séparément et invisible dans la barre d’adresse.

Avant que la première ligne de votre script s’exécute, PHP a déjà lu la requête et rempli trois tableaux.

  • $_GET — les paires nom/valeur trouvées dans la chaîne de requête ;
  • $_POST — les données envoyées dans le corps d’une requête POST avec l’encodage habituel d’un formulaire HTML;
  • $_SERVER — le contexte de la requête : méthode employée, adresse demandée, en-têtes reçus.

Modifiez la requête ci-dessous et observez les trois tableaux se remplir.

Inspecteur de requête
Modifiez la requête et observez comment PHP remplit les superglobales.
$_GET
[
    "page" => "produits",
    "categorie" => "sirop",
]
$_POST
[]   (vide : la requête n'est pas un POST)
$_SERVER (extrait)
REQUEST_METHOD => "GET"
REQUEST_URI    => "/index.php?page=produits&categorie=sirop"
QUERY_STRING   => "page=produits&categorie=sirop"

Notez que les deux chemins ne s’excluent pas. Un formulaire envoyé en POST vers traitement.php?etape=2 remplira $_POST avec ses champs et $_GET avec le paramètre de l’URL.

Le choix se discute mieux à partir de ses conséquences observables qu’à partir d’une règle apprise par cœur.

GET POST
Où voyagent les données dans l’URL dans le corps de la requête
Visible dans la barre d’adresse oui non
Peut être mise en favori, partagée par courriel oui non
Au rafraîchissement rejouée sans avertissement le navigateur demande confirmation
Taille utilisable limitée en pratique nettement plus large
Conservée dans le cache du navigateur oui non

Trois attributs suffisent à faire circuler des données : method sur le formulaire, action pour la destination, et name sur chaque champ.

<form method="post" action="traitement.php">
<label>Nom
<input type="text" name="nom">
</label>
<button type="submit">Envoyer</button>
</form>

L’attribut name est le pont entre le champ et le code PHP. Un champ sans name s’affiche normalement, se remplit normalement, et n’arrive pas au serveur — c’est l’oubli le plus fréquent, et le plus silencieux.

Du formulaire à la superglobale
L'attribut name du champ devient la clé du tableau. Survolez un champ pour voir le lien.
Le formulaire
Nom name="nom"
Courriel name="courriel"
Quantité name="quantite"
Recevoir l'infolettre name="infolettre"
Intérêts name="interets[]"
Le balisage correspondant
<form method="post" action="traitement.php">
  <label>Nom    <input type="text" name="nom">  </label>  <label>Courriel    <input type="email" name="courriel">  </label>  <label>Quantité    <input type="number" name="quantite">  </label>  <input type="checkbox" name="infolettre" value="oui"> Recevoir l'infolettre  <input type="checkbox" name="interets[]" value="sirop"> Sirop  <input type="checkbox" name="interets[]" value="miel"> Miel  <input type="checkbox" name="interets[]" value="tire"> Tire  <button type="submit">Envoyer</button>
$_GET
[]
$_POST
[  "nom" => "Pacal Vincent",  "courriel" => "pascal.vincent@exemple.test",  "quantite" => "3",  "interets" => [    "sirop",  ],]
Décochez toutes les cases : leurs clés disparaissent du tableau au lieu d'y figurer à vide. Un champ non transmis n'est pas une valeur vide, il est absent — d'où l'usage de isset() ou de l'opérateur ?? avant de lire une case à cocher.

Deux comportements méritent qu’on s’y arrête. Un nom terminé par [], comme interets[], indique à PHP de rassembler les valeurs répétées dans un tableau plutôt que de ne garder que la dernière. Et une case à cocher qui n’est pas cochée n’est pas transmise du tout : sa clé est absente du tableau, elle n’y figure pas à vide.

$nom = $_POST["nom"] ?? "";
$infolettre = isset($_POST["infolettre"]);
$interets = $_POST["interets"] ?? [];

$_SERVER ne contient pas les données saisies, mais les circonstances de la requête. Quelques entrées reviennent souvent.

Entrée Contenu
REQUEST_METHOD la méthode employée, "GET" ou "POST"
REQUEST_URI l’adresse demandée, chaîne de requête comprise
SCRIPT_NAME le chemin du fichier PHP exécuté
HTTP_USER_AGENT le navigateur annoncé par le client
REMOTE_ADDR l’adresse IP à l’origine de la requête

Plutôt que de séparer le formulaire et son traitement en deux fichiers, on écrit souvent les deux dans le même, et REQUEST_METHOD sert à distinguer les deux passages.

  1. Premier passage. La personne arrive par un lien : la requête est un GET, le formulaire s’affiche vide.

  2. Deuxième passage. Elle envoie le formulaire : la requête est un POST vers la même adresse, et le script prend cette fois la branche de traitement.

<?php
$erreurs = [];
$soumis = $_SERVER["REQUEST_METHOD"] === "POST";
if ($soumis) {
// normaliser, valider, puis agir
}
?>
<form method="post" action="">

Le balisage HTML sait déjà poser des contraintes : required, type="email", min et max. Elles rendent un vrai service, et il y a tout intérêt à les écrire : elles signalent le problème immédiatement, sans aller-retour vers le serveur, et guident la personne pendant qu’elle remplit.

Elles décrivent en revanche le comportement du navigateur. Une requête peut atteindre votre page sans être passée par lui : un outil en ligne de commande, un script, un formulaire recopié et modifié ailleurs produisent des requêtes tout aussi valides du point de vue du serveur.

Ce que le serveur vérifie
Deux façons d'envoyer les mêmes champs : par le formulaire, ou en le contournant.
Dans le navigateurcontraintes déclarées dans le balisage
Nomrequiredmaxlength="60"
Courrielrequiredtype="email"
Quantitérequiredmin="1"max="10"
Rien ne s'exécute côté serveur tant que la requête n'est pas partie.
Le bouton orange envoie un nom fait d'espaces, un courriel sans arobase et une quantité négative — trois valeurs que required, type="email" et min="1" auraient refusées dans le navigateur. Elles arrivent quand même. Les contraintes du balisage guident la personne qui remplit le formulaire ; les règles du serveur, elles, s'appliquent à toutes les requêtes.

La plupart des validations se rangent dans l’une de ces catégories.

  • Présence — le champ est-il rempli, une fois les espaces retirés ?
  • Format ou type — la valeur ressemble-t-elle à un courriel, à un entier, à une date ?
  • Plage ou longueur — le nombre est-il dans les bornes attendues, le texte pas trop long ?
  • Cohérence — les deux mots de passe correspondent-ils, la date de fin suit-elle la date de début ?

On normalise avant de contrôler : retirer les espaces de début et de fin avec trim() évite de refuser un nom qui n’a que le tort d’avoir été copié-collé avec une espace.

$nom = trim($_POST["nom"] ?? "");
$courriel = trim($_POST["courriel"] ?? "");
$quantite = trim($_POST["quantite"] ?? "");
$erreurs = [];
if ($nom === "") {
$erreurs["nom"] = "Le nom est obligatoire.";
}
if (!filter_var($courriel, FILTER_VALIDATE_EMAIL)) {
$erreurs["courriel"] = "L'adresse ne ressemble pas à un courriel.";
}
$q = filter_var($quantite, FILTER_VALIDATE_INT, [
"options" => ["min_range" => 1, "max_range" => 10],
]);
if ($q === false) {
$erreurs["quantite"] = "La quantité doit se situer entre 1 et 10.";
}
if (!isset($_POST["conditions"])) {
$erreurs["conditions"] = "Les conditions doivent être acceptées.";
}

Les deux opérations sont faciles à confondre, alors qu’elles répondent à des questions différentes et n’ont pas lieu au même moment.

Validation Échappement
Question posée cette valeur est-elle acceptable ? comment l’écrire sans qu’elle soit interprétée ?
Moment à la réception à l’affichage
Résultat un message d’erreur, ou rien du texte transformé pour la sortie

Une valeur peut être parfaitement valide et rester dangereuse à afficher telle quelle. htmlspecialchars() reste donc nécessaire au moment d’écrire dans la page, y compris pour des données qui ont passé toutes les validations.

Quand des erreurs subsistent, le formulaire revient — et il gagne à revenir rempli. Retaper huit champs parce que l’un d’eux posait problème décourage vite.

<input type="text" name="nom"
value="<?php echo htmlspecialchars($nom); ?>">
<?php if (isset($erreurs["nom"])): ?>
<p class="erreur"><?php echo htmlspecialchars($erreurs["nom"]); ?></p>
<?php endif; ?>
VérificationVos champs portent déjà required et le type email. Pourquoi revalider côté serveur ?

Parce que ces attributs décrivent ce que fait le navigateur avant d’envoyer. La requête, elle, peut être construite ailleurs : par un outil en ligne de commande, un script, ou une copie modifiée de votre formulaire. Le serveur est le seul endroit que toutes les requêtes traversent, quelles que soient leur provenance. Cela ne rend pas les contraintes HTML inutiles pour autant : elles restent le moyen le plus rapide de signaler un problème à la personne qui remplit.

Une fois les données validées et l’action effectuée, il reste à répondre. Répondre directement par une page de confirmation pose un problème discret.

  1. Navigateur vers Serveur : POST /commande.php
  2. Serveur : enregistre la commande
  3. Serveur vers Navigateur : 200 · page de confirmation
  4. Navigateur vers Serveur : POST /commande.php (la personne rafraîchit la page)
  5. Serveur : enregistre une deuxième commande
  6. Serveur vers Navigateur : 200 · page de confirmation
Sans redirection : le rafraîchissement rejoue la soumission

La page affichée est la réponse à un POST. La rafraîchir peut demander au navigateur de renvoyer ce même POST, ce qui répète alors l’action. Une réponse de redirection règle la situation.

  1. Navigateur vers Serveur : POST /commande.php
  2. Serveur : enregistre la commande
  3. Serveur vers Navigateur : 303 · Location: merci.php
  4. Navigateur vers Serveur : GET /merci.php (suivi automatiquement par le navigateur)
  5. Serveur vers Navigateur : 200 · page de remerciement
  6. Navigateur vers Serveur : GET /merci.php (la personne rafraîchit : simple relecture)
  7. Serveur vers Navigateur : 200 · page de remerciement
Avec redirection : le rafraîchissement ne rejoue qu'un GET

Ce déroulement porte un nom : POST-Redirect-GET. Le POST agit, la redirection déplace le navigateur, et la page finalement affichée est le résultat d’un GET ordinaire — rejouable sans effet.

if ($soumis && empty($erreurs)) {
// enregistrer la commande
header("Location: merci.php", true, 303);
exit;
}

Le code 303 indique explicitement au navigateur d’aller chercher la nouvelle adresse en GET. Le code 302, plus ancien, est encore très répandu et fonctionne de la même façon dans la pratique.

Notez que l’on redirige en cas de succès seulement. En cas d’erreur, on réaffiche la page avec les messages, sans rediriger : la redirection ferait perdre $erreurs et les valeurs saisies, puisque le script suivant repartirait de zéro. Les transporter d’une requête à l’autre demanderait une session, que nous verrons plus loin.

D'un formulaire vide à la page de confirmation1. Afficher le formulaire : Première requête en GET : la page renvoie un formulaire vide. 2. Soumettre : Le navigateur transmet chaque champ sous son attribut name. 3. Recevoir la requête : PHP remplit $_GET, $_POST et $_SERVER avant la première ligne du script. 4. Normaliser et valider : On contrôle présence, format, plage et cohérence ; les erreurs s'accumulent. Les contraintes du balisage ne garantissent pas ce qui arrive au serveur. 5. Réafficher ou agir : S'il reste des erreurs, la page revient avec les messages et les valeurs saisies. 6. Rediriger : Sinon, une réponse 303 renvoie vers une page consultable en GET. 7. Page de confirmation : Le rafraîchissement ne rejoue plus la soumission, seulement une lecture.Afficher le formulairePremière requête en GET : la page renvoie un formulaire vide.SoumettreLe navigateur transmet chaque champ sous son attribut name.Recevoir la requêtesuperglobalesPHP remplit $_GET, $_POST et $_SERVER avant la première ligne du script.Normaliser et validervalidationOn contrôle présence, format, plage et cohérence ; les erreurs s'accumulent.⚠Les contraintes du balisage ne garantissent pas ce qui arrive au serveur.Réafficher ou agirS'il reste des erreurs, la page revient avec les messages et les valeurs saisies.RedirigerSinon, une réponse 303 renvoie vers une page consultable en GET.Page de confirmationLe rafraîchissement ne rejoue plus la soumission, seulement une lecture.

Un formulaire n’est qu’une façon, parmi d’autres, d’envoyer des données. Une API reçoit des requêtes émises non par un navigateur, mais par un autre programme (application mobile, autre serveur, code JavaScript). Le principe reste le même — une requête, une méthode, une réponse — mais les données échangées sont généralement du JSON plutôt que du HTML.

<?php
// Au lieu de renvoyer du HTML, on renvoie du JSON.
header("Content-Type: application/json");
echo json_encode(["statut" => "ok", "message" => "Reçu"]);
<?php
// Et on lit un corps JSON envoyé par le client (au lieu de $_POST).
$donnees = json_decode(file_get_contents("php://input"), true);
  • Un champ sans name n’arrive pas au serveur, même s’il s’affiche et se remplit correctement.
  • Une case décochée est absente, pas vide : isset() ou ?? avant de la lire.
  • Normaliser avant de valider : pour du texte, trim() d’abord, contrôles ensuite. La normalisation dépend du type de donnée.
  • Les contraintes du balisage guident, les règles du serveur décident. Écrire les deux, se fier aux secondes.
  • Valider et échapper sont deux moments distincts : à la réception pour l’un, à l’affichage pour l’autre.
  • Rediriger après un POST réussi, avec exit derrière header(), et sans la moindre sortie avant l’appel.
  • Réafficher les valeurs saisies en cas d’erreur, passées par htmlspecialchars().
  • Les données d’une requête voyagent soit dans l’URL, soit dans le corps ; PHP les met à disposition dans $_GET et $_POST, et décrit le contexte dans $_SERVER.
  • Ces trois tableaux reflètent la requête en cours et sont reconstruits à chaque nouvelle requête.
  • Le choix entre GET et POST se raisonne à partir des conséquences : adresse partageable et rejouable d’un côté, action qu’il vaut mieux ne pas répéter de l’autre.
  • L’attribut name d’un champ devient la clé dans le tableau reçu ; un nom terminé par [] produit un tableau de valeurs.
  • La validation côté serveur s’applique à toutes les requêtes, y compris à celles qui ne sont pas passées par le formulaire. Elle se distingue de l’échappement, qui intervient à l’affichage.
  • Le patron POST-Redirect-GET évite qu’un rafraîchissement ne rejoue l’action, en répondant par une redirection plutôt que par une page.

Quiz. Une case à cocher nommée infolettre n'est pas cochée. Que contient $_POST['infolettre'] ?

Quiz. Vos champs portent déjà required et min='1'. Que peut-on en conclure sur les données reçues ?

Quiz. À quoi sert la redirection dans le patron POST-Redirect-GET ?

Quiz. Une donnée a passé toutes vos validations. Faut-il encore l'échapper avant de l'afficher ?