Version en ligne

Tutoriel : Éviter le renvoi répétitif d'un formulaire en rafraîchissant

Table des matières

Éviter le renvoi répétitif d'un formulaire en rafraîchissant
L'origine du message
La solution
Mise en pratique de la méthode
Exemple concret avec le minichat

Éviter le renvoi répétitif d'un formulaire en rafraîchissant

L'origine du message

Message d'avertissement POST

Que voilà un simple message d'avertissement, pourtant source de bien des ennuis.
Il existe malgré tout un moyen efficace de l'éviter, qui n'est malheureusement pas assez connu sur le web. Voilà pourquoi j'ai souhaité écrire ce tutoriel, en espérant aider quelques programmeurs désespérés.
En contournant cet avertissement, vous empêchez le renvoi répétitif de formulaires, évitant ainsi de lourds scripts comparant la dernière entrée de vos tables avec celle que vous tentez d'insérer, en prenant l'exemple d'un minichat.

Je vous recommande fortement d'avoir pris connaissance des trois premiers chapitres du tutoriel de M@teo21 sur le PHP, d'être assez à l'aise avec les formulaires ainsi que les sessions, et par la même occasion d'être en bonne forme. :D

L'origine du message

La solution

Ce message d'avertissement n'est en réalité rien d'autre qu'une sorte d'erreur envoyée par le navigateur. Ce dernier souhaite vous avertir qu'en rafraîchissant la page où vous vous trouvez, en retournant à la précédente ou en allant à la suivante, vous allez renvoyer le contenu d'un formulaire que vous avez déjà rempli et validé.
Ce n'est qu'une protection formelle contre, comme il l'indique, d'éventuelles répétitions d'achat sur un site de vente, ou autre (c'est qu'en effet, il serait idiot de payer deux fois le même objet, ou d'effectuer deux fois la même transaction sur le site de sa banque ^^ ).


La solution

La solution

L'origine du message Mise en pratique de la méthode

Pour éviter ce message d'avertissement, il existe une technique simple : la redirection, qu'on pourrait appeler dans le cas présent "le rafraîchissement forcé". En effet, une fois qu'un visiteur quelconque enverra un formulaire, nous allons, avant de traiter le contenu de ce dernier, rafraîchir la page où le formulaire a conduit le visiteur (celle qui est indiquée dans <form action=""> ), ce qui aura pour effet de "contourner" le message d'avertissement : le principe est aussi simple que cela.
Il existe plusieurs méthodes pour rediriger un visiteur : en HTML, en Javascript, et bien sûr en PHP. Nous ne verrons que cette dernière, les autres étant réservées à des situations précises.

En PHP, il n'existe aucune fonction toute faite pour rediriger. Mais nous pouvons utiliser à bon escient la fonction header() , dont le rôle est d'envoyer au navigateur des "en-têtes http". Rassurez-vous, elle est déjà toute préparée ; nous allons juste utiliser une syntaxe bien précise de cette dernière, qui est :

<?php
header('Location: monFichier.php');
?>

Mais cette fonction est un peu capricieuse, surtout de la façon dont nous nous en servons. En effet, il faut retenir trois choses, qui seront vraiment déterminantes dans la suite de ce tuto.

Afin que tout soit bien clair dans vos esprits, voici un petit schéma qui résume ces trois points et la façon de les appliquer à nos codes. Lisez-le bien, il résume presque à lui seul tout le tuto, ou du moins la partie théorique.

Schéma traitement formulaire

L'origine du message Mise en pratique de la méthode

Mise en pratique de la méthode

La solution Exemple concret avec le minichat

Voilà maintenant un petit moment qu'on réfléchit à comment résoudre notre problème, alors je pense qu'il est maintenant temps qu'on mette tout ça sur papier, noir sur blanc comme on dit. :)
En réalité, si on lit bien le schéma, on remarque qu'il ne nous manque qu'un seul bout de code pour éviter le message d'avertissement. Si vous n'avez pas encore saisi ça, alors jetez-y une nouvelle fois un p'tit coup d'oeil, et regardez bien les différences entre avant et après.
Étant donné qu'on a toutes les cartes en main, je vais vous donner ce code et le décortiquer partie par partie, pour que vous compreniez bien ses subtilités d'emploi.

<?php

// Inutile d'expliquer la présence du session_start().
session_start();

// { Début - Première partie
if(!empty($_POST) OR !empty($_FILES))
{
    $_SESSION['sauvegarde'] = $_POST ;
    $_SESSION['sauvegardeFILES'] = $_FILES ;
    
    $fichierActuel = $_SERVER['PHP_SELF'] ;
    if(!empty($_SERVER['QUERY_STRING']))
    {
        $fichierActuel .= '?' . $_SERVER['QUERY_STRING'] ;
    }
    
    header('Location: ' . $fichierActuel);
    exit;
}
// } Fin - Première partie

// { Début - Seconde partie
if(isset($_SESSION['sauvegarde']))
{
    $_POST = $_SESSION['sauvegarde'] ;
    $_FILES = $_SESSION['sauvegardeFILES'] ;
    
    unset($_SESSION['sauvegarde'], $_SESSION['sauvegardeFILES']);
}
// } Fin - Seconde partie

?>

Comme vous le voyez, j'ai découpé ce code en deux parties distinctes. Expliquons donc comment tout ça fonctionne.

Première partie

  1. if(!empty($_POST) OR !empty($_FILES)) : si le visiteur a envoyé un formulaire quelconque, alors [...]. Notez que j'ai utilisé ici un !empty() au lieu d'un isset() . En effet, $_POST et $_FILES sont ce qu'on appelle des superglobales, et les superglobales ont la particularité de toujours exister, quoi qu'il advienne. Utiliser un if(isset($_POST) OR isset($_FILES)) aurait donc été inutile. Dans la suite du code, nous allons rencontrer une autre superglobale qui est $_SERVER ; ne vous étonnez donc pas si j'utilise encore un !empty() au lieu d'un isset() .
    Pour ceux qui n'auraient jamais rencontré la fonction empty() : elle sert à vérifier si une variable ou un tableau est vide. Dans notre cas, on vérifie donc la condition : "si $_POST ou $_FILES contiennent quelque chose", ou formulé en français : "si on a envoyé un formulaire" (voir la définition de la documentation pour plus de précisions sur empty() ).

  2. <?php
    $_POST = $_SESSION['sauvegarde'] ;
    $_FILES = $_SESSION['sauvegardeFILES'] ;
    ?>

    Ici on sauvegarde le formulaire et les éventuels fichiers envoyés dans des variables de session, comme expliqué précédemment.

  3. <?php
    $fichierActuel = $_SERVER['PHP_SELF'] ;
    
    if(!empty($_SERVER['QUERY_STRING']))
    {
        $fichierActuel .= '?' . $_SERVER['QUERY_STRING'] ;
    }
    ?>

    Dans cette partie, on détermine le fichier où est le visiteur, pour pouvoir rafraîchir la page. Il faut d'abord savoir que la variable $_SERVER['PHP_SELF'] renvoie le "fichier courant" utilisé (par exemple page.php) et que $_SERVER['QUERY_STRING'] renvoie toutes les informations qui sont dans l'URL, autre que le nom du fichier (par exemple, si je suis sur index.php?new=24&amp;page=3, cette variable contiendra new=24&amp;page=3).
    Ces deux variables combinées permettent donc de recomposer l'adresse exacte du fichier où se trouve le visiteur. Traduit en français, le code donne :

    • je donne à la variable $fichierActuel le nom du fichier où le visiteur se trouve,

    • si on a d'autres renseignements plus précis sur la localisation du visiteur, on les ajoute au nom du fichier. Nous n'avons pas besoin d'utiliser de urlencode() étant donné que la variable $_SERVER['QUERY_STRING'] fait tout pour nous.

    Si le .= vous intrigue, revoyez un peu la concaténation.

  4. <?php
    header('Location: ' . $fichierActuel);
    exit;
    ?>

    Comme vu dans le paragraphe précédent, on rafraîchit pour éviter le message d'avertissement, et on place un exit; pour dire à PHP que c'est la fin du voyage. :)

Seconde partie

  1. if(isset($_SESSION['sauvegarde'])) : si un visiteur vient d'être redirigé, alors ...

  2. <?php
    $_POST = $_SESSION['sauvegarde'] ;
    $_FILES = $_SESSION['sauvegardeFILES'] ;
    ?>

    ... on récupère le formulaire et les éventuels fichiers qu'il a envoyé pour réinjecter le tout dans les variables $_POST et $_FILES . Attention : comme dit précédemment, les deux variables qu'on utilise ici ne sont pas les vraies variables $_POST et $_FILES , mais juste des intermédiaires pour réintroduire le formulaire envoyé.

  3. unset($_SESSION['sauvegarde'], $_SESSION['sauvegardeFILES']) : Pour finir, on supprime les variables de session qui ont servi à sauvegarder le formulaire, pour ne pas le renvoyer une nouvelle fois si le visiteur rafraîchit la page.

OK, mais j'en fais quoi maintenant de ton bout de code ?

En effet, une après avoir compris comment fonctionne et à quoi sert ce code, vous vous demandez sûrement : on en fait quoi ? En réalité il vous suffit de le placer dans les premières parties de vos fichiers (de préférence au tout début), comme indiqué sur le schéma.
Ensuite, celui-ci se chargera de tout, sans que vous n'ayez besoin de retoucher quoi que ce soit dans la structure même de vos fichiers actuels. La meilleure des solutions serait même de créer un nouveau fichier ne contenant que ces quelques lignes, et l'inclure au début de chacun de vos autres fichiers.


La solution Exemple concret avec le minichat

Exemple concret avec le minichat

Mise en pratique de la méthode

Je sais que tout cela est encore embrouillé dans vos esprits, c'est pourquoi je pense qu'un petit exercice d'application sur un exemple concret et sur lequel vous avez déjà travaillé ne ferait pas de mal : un minichat.
Je ne copierai pas exactement celui du TP proposé par M@teo21, du fait qu'il ne reprenne pas tout ce que j'aurais envie de vous montrer, mais je m'en inspirerai.

En premier lieu, je vous montrerai le code du minichat sans notre méthode pour éviter le renvoi du message, tout en l'expliquant avec des commentaires. Je le comparerai ensuite à un minichat avec notre méthode, pour que vous voyiez bien les différences. Cette seconde partie sera composée de deux fichiers, l'un avec le code du minichat, et l'autre avec le code servant à la redirection, qui sera inclus dans le premier.

Minichat simple

<?php

// Le minichat ne nécessite pas de session_start(), étant donné qu'il ne manipule pas les sessions.

// Une connexion classique à un serveur MySQL, rien de particulier.
mysql_connect('localhost', 'sdz', 'motDePasse');
mysql_select_db('coursphp');

// Une petite fonction qui allège un peu les codes du traitement d'un formulaire. Elle sert ici plus d'exemple qu'autre chose.
function securisation($variable)
{
	$variable = mysql_real_escape_string($variable);
	$variable = htmlspecialchars($variable);
	
	return $variable ;
}

?>
<!-- Des en-têtes HTML normaux, rien à expliquer. -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
    <title>Minichat</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>

<body>

<?php

// Le traitement du formulaire d'ajout d'un message.
if(isset($_POST['pseudo'], $_POST['message'])) // Si le formulaire est envoyé.
{
    if(!empty($_POST['pseudo']) AND !empty($_POST['message'])) // Si tous les champs ont été remplis correctement.
    {
        $pseudo = securisation($_POST['pseudo']); // Regardez un peu plus haut pour la fonction.
        $message = securisation($_POST['message']);
        
        // Et une insertion dans la table minichat.
        mysql_query('INSERT INTO minichat VALUES("", "' . $pseudo . '", "' . $message . '")') OR die(mysql_error());
    }
    else // Si le visiteur n'a pas correctement saisi tous les champs, alors on le lui indique.
    {
        echo '<strong>Erreur : vous n\'avez pas correctement saisi les champs obligatoires.</strong>' ;
    }
}

?>

<!-- Notre formulaire, sans commentaire nécessaire. -->
<form action="minichat.php" method="post">
<p>
Pseudo : <input type="text" name="pseudo" /><br />
Message : <input type="text" name="message" /><br />
<input type="submit" value="Envoyer" />
</p>
</form>

<!-- Puis, normalement, le listage des messages du minichat. On passera cette partie, étant donné qu'elle ne nous intéresse pas. -->

</body>
</html>

Il n'y a pas grand chose à ajouter, mis à part l'utilisation de la fonction securisation() qui pourrait éventuellement vous perturber, mais elle est plus là en guise d'exemple qu'autre chose.
Passons sans plus attendre au code avec la méthode évitant le renvoi du formulauire.

Minichat évitant le message d'avertissement

<?php

include('eviterMessageAvertissement.php');

// Connexion au serveur MySQL.
mysql_connect('localhost', 'sdz', 'motDePasse');
mysql_select_db('coursphp');

/* [...]
Et je zappe volontairement tout le reste du code, non seulement pour économiser de la place,
mais aussi pour insister sur le fait que le code reste exactement le même et que le fichier
(que j'ai ici appelé "eviterMessageAvertissement.php") est bien inclus avant toute autre chose.
*/

?>
<?php

session_start();

if(!empty($_POST) OR !empty($_FILES))
{
    $_SESSION['sauvegarde'] = $_POST ;
    $_SESSION['sauvegardeFILES'] = $_FILES ;
    
    $fichierActuel = $_SERVER['PHP_SELF'] ;
    if(!empty($_SERVER['QUERY_STRING']))
    {
        $fichierActuel .= '?' . $_SERVER['QUERY_STRING'] ;
    }
    
    header('Location: ' . $fichierActuel);
    exit;
}

if(isset($_SESSION['sauvegarde']))
{
    $_POST = $_SESSION['sauvegarde'] ;
    $_FILES = $_SESSION['sauvegardeFILES'] ;
    
    unset($_SESSION['sauvegarde'], $_SESSION['sauvegardeFILES']);
}

?>

Si vous êtes intrigués par le fait que j'ai coupé toute la suite du fichier minichat.php , je ne peux que vous recommander de revoir le schéma, ainsi que le chapitre précédent (la "Mise en pratique de la méthode").

Nous voici donc à la fin de ce tutoriel. J'ai mis beaucoup de cœur et d'efforts à le rédiger, j'espère donc avoir été suffisamment clair dans mes explications et mes exemples.
Si, malgré tout, certains points vous semblent encore obscurs, n'hésitez pas à poster un commentaire ou à m'envoyer un MP, ce qui me permettra d'améliorer le tuto et en fera profiter tout le monde. :)

Pour finir, je souhaiterais tout particulièrement remercier l'équipe des zCorrecteurs, qui font vraiment un boulot formidable. :)


Mise en pratique de la méthode