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
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 ^^ ).
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.
D'une, la fonction header() est allergique à tout code HTML. En d'autres termes, elle fonctionne de la même façon que set_cookie() : il ne faut placer aucun code HTML ni aucun texte avant son utilisation (pour plus de précisions, lisez la description de la fonction header() que fournit la documentation). Il faudra donc l'utiliser au début de nos scripts.
Deuxièmement, nous devrons toujours placer un exit; juste après son utilisation. En effet, lorsque PHP lit le script, il voit la fonction header() et se dit : "Je retiens qu'après l'exécution totale du script, il faudra que je redirige notre visiteur sur telle page". Ainsi, en plaçant un exit; juste après, nous précisons à PHP que le script s'arrête là, et qu'il ne faut pas regarder ce qui suit.
Pour finir, en redirigeant, la fonction header() va vider les variables $_POST et $_FILES qui constituent notre formulaire. Pour éviter de perdre ces informations, nous allons les sauvegarder dans des variables de session (par exemple, $_SESSION['sauvegarde'] et $_SESSION['sauvegardeFILES'] ) avant la redirection, et les "réinjecter" ensuite dans d'autres variables. Pour éviter que vous n'ayez à changer toute la structure de vos fichiers, nous allons appeler ces variables $_POST et $_FILES . Petite parenthèse au passage : vous vous demandez sûrement pourquoi j'introduis ici $_FILES , alors que, pourtant, c'est bien $_POST qui contient notre formulaire. En fait, $_FILES a comme contenu des informations relatives aux éventuelles images envoyées avec le formulaire. Vous pouvez revoir ce chapitre si vous ne vous en souvenez plus.
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.
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
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() ).
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&page=3, cette variable contiendra new=24&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.
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
if(isset($_SESSION['sauvegarde'])) : si un visiteur vient d'être redirigé, alors ...
... 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é.
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.
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.
*/
?>
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. :)