Traitement des formulaires

Résumé Support Quiz

Dans ce chapitre, nous allons voir comment traiter des formulaires en PHP. Pour comprendre le fonctionnement, on va reprendre un exercice simple : faire deviner un chiffre à l'utilisateur, puis utiliser ce principe pour construire un formulaire plus complet qui permet de composer une glace et d'en calculer le prix.

Créer un premier formulaire

On commence par créer une page jeu.php avec une valeur à deviner.

<?php $aDeviner = 150; ?>

L'objectif est de permettre à l'utilisateur de saisir une valeur, puis de lui indiquer si le chiffre proposé est trop petit, trop grand ou correct.

En ligne de commande, on pouvait utiliser readline(). Dans le cadre d'un site web, l'interaction avec l'utilisateur passe par un formulaire HTML.

<form action="/jeu.php" method="get"> <input type="number" name="chiffre" placeholder="Entre 0 et 1000"> <button type="submit">Deviner</button> </form>

La balise form accepte notamment deux attributs importants :

  • action, qui indique la page appelée lors de la soumission du formulaire. Si on ne met rien, la page courante est appelée ;
  • method, qui indique la méthode HTTP utilisée. On peut utiliser get ou post. Par défaut, si rien n'est précisé, la méthode est get.

Le champ input possède aussi un attribut important : name. C'est ce nom qui sera utilisé par PHP pour retrouver la valeur envoyée par le formulaire.

Récupérer les données avec $_GET

Avec une méthode get, les données du formulaire apparaissent dans l'URL après un point d'interrogation.

Par exemple, si l'utilisateur saisit 100, on obtient une URL qui ressemble à ceci :

/jeu.php?chiffre=100

PHP met automatiquement ces valeurs à disposition dans la variable globale $_GET.

var_dump($_GET);

On récupère alors un tableau où la clé correspond au nom du champ (chiffre) et la valeur correspond à ce que l'utilisateur a saisi.

$_GET['chiffre']

Attention : même si le champ HTML est de type number, la valeur récupérée dans $_GET reste une chaîne de caractères. Si on veut travailler avec un entier, il faudra donc convertir la valeur.

Afficher un message à l'utilisateur

On peut maintenant comparer la valeur proposée avec le chiffre à deviner.

<?php if ($_GET['chiffre'] > $aDeviner): ?> Votre chiffre est trop grand <?php elseif ($_GET['chiffre'] < $aDeviner): ?> Votre chiffre est trop petit <?php else: ?> Bravo ! Vous avez deviné le chiffre <?= $aDeviner ?> <?php endif ?>

Cette première version fonctionne, mais elle pose rapidement deux problèmes :

  • si l'utilisateur arrive sur la page sans paramètre dans l'URL, $_GET['chiffre'] n'existe pas ;
  • si on réaffiche directement une valeur saisie par l'utilisateur dans le HTML, on peut casser la structure de la page.

Ne jamais faire confiance à l'utilisateur

Si on veut réafficher la valeur saisie dans le champ, on peut être tenté d'écrire :

<input type="number" name="chiffre" value="<?= $_GET['chiffre'] ?>">

Le problème, c'est que l'utilisateur peut modifier directement l'URL et envoyer n'importe quelle valeur. Par exemple, il pourrait injecter des guillemets ou des balises HTML, ce qui peut casser le code de la page.

Il faut donc toujours filtrer ou convertir les données venant d'un formulaire ou de l'URL.

Dans notre cas, si on attend un entier, on peut convertir la valeur :

$value = (int)$_GET['chiffre'];

Pour un champ texte, on ne peut pas simplement convertir en chaîne. Il faut échapper les caractères HTML avec une fonction comme htmlentities().

htmlentities($_GET['chiffre'])

Cela convertit les caractères spéciaux en entités HTML. Par exemple, un guillemet ou un chevron ne sera plus interprété comme du code HTML.

Vérifier l'existence d'une valeur

Quand on arrive sur la page /jeu.php sans paramètre, le tableau $_GET est vide. Si on essaie de lire $_GET['chiffre'], PHP peut afficher une alerte Undefined index chiffre suivant la configuration des erreurs.

Pour éviter ce problème, on vérifie d'abord que la clé existe avec isset().

<?php if (isset($_GET['chiffre'])): ?> <?php if ($_GET['chiffre'] > $aDeviner): ?> Votre chiffre est trop grand <?php elseif ($_GET['chiffre'] < $aDeviner): ?> Votre chiffre est trop petit <?php else: ?> Bravo ! Vous avez deviné le chiffre <?= $aDeviner ?> <?php endif ?> <?php endif ?>

Il faudra aussi appliquer cette vérification lorsqu'on réaffiche la valeur dans le champ.

<input type="number" name="chiffre" value="<?php if (isset($_GET['chiffre'])) { echo htmlentities($_GET['chiffre']); } ?>" >

Séparer la logique de l'affichage

Mélanger beaucoup de PHP et de HTML peut rendre le code difficile à lire. Une bonne habitude consiste à placer la logique en haut du fichier, puis à utiliser des variables simples dans la partie HTML.

<?php $aDeviner = 150; $error = null; $success = null; $value = null; if (isset($_GET['chiffre'])) { $value = (int)$_GET['chiffre']; if ($value > $aDeviner) { $error = 'Votre chiffre est trop grand'; } elseif ($value < $aDeviner) { $error = 'Votre chiffre est trop petit'; } else { $success = "Bravo, vous avez deviné le chiffre <strong>$aDeviner</strong>"; } } ?>

Ensuite, dans le HTML, on se contente d'afficher ces variables.

<?php if ($error): ?> <div class="alert alert-danger"> <?= $error ?> </div> <?php elseif ($success): ?> <div class="alert alert-success"> <?= $success ?> </div> <?php endif ?> <form action="/jeu.php" method="get"> <input type="number" name="chiffre" value="<?= $value ?>"> <button type="submit">Deviner</button> </form>

On obtient un code plus simple à relire : en haut, on prépare les variables ; dans le HTML, on affiche le résultat.

GET ou POST ?

Un formulaire peut aussi utiliser la méthode post.

<form action="/jeu.php" method="post"> <input type="number" name="chiffre"> <button type="submit">Deviner</button> </form>

Avec post, les données ne sont plus visibles dans l'URL. Elles sont envoyées dans le corps de la requête. On peut le voir dans l'onglet Réseau / Network de l'inspecteur du navigateur.

Côté PHP, on récupère alors les données avec $_POST au lieu de $_GET.

var_dump($_POST);

Le tableau fonctionne de la même manière : la clé correspond au nom du champ, et la valeur correspond à ce qui a été saisi.

Pour choisir entre get et post, on peut retenir cette règle :

  • utilisez get si les paramètres peuvent apparaître dans l'URL et si l'URL doit pouvoir être partagée, comme pour une recherche ;
  • utilisez plutôt post si les informations ne doivent pas apparaître dans l'URL, par exemple pour un formulaire de connexion.

Avec post, si on recharge une page issue d'une soumission de formulaire, le navigateur peut demander une confirmation pour renvoyer les données.

Envoyer plusieurs valeurs

Certains champs peuvent envoyer plusieurs valeurs. C'est le cas des cases à cocher lorsqu'on autorise plusieurs choix.

<input type="checkbox" name="parfum" value="fraise"> Fraise <input type="checkbox" name="parfum" value="vanille"> Vanille <input type="checkbox" name="parfum" value="chocolat"> Chocolat

Avec ce code, si on coche plusieurs cases, on ne récupère qu'une seule valeur. Pour indiquer à PHP que le champ doit être traité comme un tableau, on ajoute des crochets au nom.

<input type="checkbox" name="parfum[]" value="fraise"> Fraise <input type="checkbox" name="parfum[]" value="vanille"> Vanille <input type="checkbox" name="parfum[]" value="chocolat"> Chocolat

On récupère alors un tableau dans $_GET ou $_POST.

$_GET['parfum'] // ['fraise', 'vanille']

Cela fonctionne avec les checkbox, mais aussi avec d'autres champs si plusieurs champs portent le même nom avec des crochets.

Attention cependant : si aucune case n'est cochée, rien n'est envoyé à PHP. La clé n'existera donc pas dans le tableau.

Conserver les cases cochées

Après la soumission du formulaire, les cases se décochent par défaut. Pour les conserver, il faut ajouter l'attribut checked lorsque la valeur fait partie des données envoyées.

Pour éviter de répéter beaucoup de conditions dans le HTML, on peut créer une fonction.

function checkbox(string $name, string $value, array $data): string { $attributes = ''; if (isset($data[$name]) && in_array($value, $data[$name])) { $attributes = ' checked'; } return <<<HTML <input type="checkbox" name="{$name}[]" value="$value"$attributes> HTML; }

La fonction in_array() permet de vérifier si une valeur existe dans un tableau. Elle prend en premier paramètre la valeur recherchée, puis le tableau dans lequel on cherche.

Pour un champ radio, la logique est proche, mais il n'y a qu'une seule valeur possible. On compare donc directement la valeur envoyée.

function radio(string $name, string $value, array $data): string { $attributes = ''; if (isset($data[$name]) && $data[$name] === $value) { $attributes = ' checked'; } return <<<HTML <input type="radio" name="$name" value="$value"$attributes> HTML; }

Exemple : composer une glace

Pour pratiquer, on peut créer un formulaire qui permet de composer une glace. On part de plusieurs tableaux de configuration.

$parfums = [ 'fraise' => 4, 'chocolat' => 5, 'vanille' => 3, ]; $cornets = [ 'pot' => 2, 'cornet' => 3, ]; $supplements = [ 'pépites de chocolat' => 1, 'chantilly' => 0.5, ];

Les parfums et les suppléments sont affichés avec des checkbox, car l'utilisateur peut en choisir plusieurs. Le cornet est affiché avec des boutons radio, car on ne peut choisir qu'un pot ou un cornet.

On utilise la méthode get, car l'objectif est de pouvoir partager l'URL de la glace composée.

<form action="" method="get"> <h2>Choisissez vos parfums</h2> <?php foreach ($parfums as $parfum => $prix): ?> <label> <?= checkbox('parfum', $parfum, $_GET) ?> <?= $parfum ?> - <?= $prix ?> € </label> <?php endforeach ?> <h2>Choisissez votre cornet</h2> <?php foreach ($cornets as $cornet => $prix): ?> <label> <?= radio('cornet', $cornet, $_GET) ?> <?= $cornet ?> - <?= $prix ?> € </label> <?php endforeach ?> <h2>Choisissez vos suppléments</h2> <?php foreach ($supplements as $supplement => $prix): ?> <label> <?= checkbox('supplement', $supplement, $_GET) ?> <?= $supplement ?> - <?= $prix ?> € </label> <?php endforeach ?> <button type="submit">Composer ma glace</button> </form>

Dans l'URL, on retrouvera les différents choix de l'utilisateur, ce qui permet de copier le lien et de partager exactement la même composition.

Calculer le prix

Pour calculer le prix, on peut préparer deux variables :

$ingredients = []; $total = 0;

Ensuite, on parcourt les choix de l'utilisateur. Il faut toujours vérifier que les valeurs reçues existent bien dans nos tableaux de configuration. Sinon, quelqu'un pourrait modifier l'URL à la main et envoyer une valeur qui n'existe pas, ce qui provoquerait une erreur Undefined index.

if (isset($_GET['parfum'])) { foreach ($_GET['parfum'] as $parfum) { if (isset($parfums[$parfum])) { $ingredients[] = $parfum; $total += $parfums[$parfum]; } } } if (isset($_GET['supplement'])) { foreach ($_GET['supplement'] as $supplement) { if (isset($supplements[$supplement])) { $ingredients[] = $supplement; $total += $supplements[$supplement]; } } } if (isset($_GET['cornet'])) { $cornet = $_GET['cornet']; if (isset($cornets[$cornet])) { $ingredients[] = $cornet; $total += $cornets[$cornet]; } }

On peut ensuite afficher le récapitulatif.

<ul> <?php foreach ($ingredients as $ingredient): ?> <li><?= $ingredient ?></li> <?php endforeach ?> </ul> <p><strong>Prix :</strong> <?= $total ?> €</p>

Il est possible d'optimiser ce code pour éviter les répétitions entre les parfums, les suppléments et le cornet. Mais cette optimisation rend la logique plus abstraite. Si vous débutez, l'important est surtout de comprendre les étapes : récupérer les données, vérifier qu'elles sont valides, puis calculer le total.

Ressources