Faire patienter l'utilisateur pendant l’exécution d’un script PHP

mercredi 1 août 2007

Un script PHP s’exécute sur le serveur, avant d’afficher à l’écran le résultat de son exécution. Dans la plupart des cas, le programme s’exécute en quelques millisecondes et l’affichage survient sans délai perceptible. Pourtant, il peut arriver qu’un script soit très long à exécuter : traitements mathématiques complexes (p. ex. lors de l’application d’un filtre complexe sur une image, un extrait sonore ou une vidéo), accès à un fichier sur l’ordinateur de l’utilisateur (p. ex. lorsque l’utilisateur upload un fichier sur le serveur), ou encore connexion à des serveurs distants (si un serveur distant ne répond pas, le script attendra le timeout pour passer au serveur suivant). Dans tous ces cas, il est nécessaire de prévenir le visiteur que le programme est toujours en cours d’exécution et qu’il doit patienter jusqu’à la fin de l’exécution. Il y a de nombreuses méthodes pour faire cela, des plus simples aux plus complexes. Cet article vous présente une méthode simple et efficace, écrite en PHP et utilisant JavaScript.

Un script PHP s’exécute sur le serveur, avant d’afficher à l’écran le résultat de son exécution. Dans la plupart des cas, le programme s’exécute en quelques millisecondes et l’affichage survient sans délai perceptible. Pourtant, il peut arriver qu’un script soit très long à exécuter : traitements mathématiques complexes (p. ex. lors de l’application d’un filtre complexe sur une image, un extrait sonore ou une vidéo), accès à un fichier sur l’ordinateur de l’utilisateur (p. ex. lorsque l’utilisateur upload un fichier sur le serveur), ou encore connexion à des serveurs distants (si un serveur distant ne répond pas, le script attendra le timeout pour passer au serveur suivant). Dans tous ces cas, il est nécessaire de prévenir le visiteur que le programme est toujours en cours d’exécution et qu’il doit patienter jusqu’à la fin de l’exécution. Il y a de nombreuses méthodes pour faire cela, des plus simples aux plus complexes. Cet article vous présente une méthode simple et efficace, écrite en PHP et utilisant JavaScript.

<h2> Principe de fonctionnement
</h2>

Le principe de fonctionnement de ce script est simple : il s’agit d’afficher au début de l’exécution du script PHP un message avertissant l’utilisateur que le traitement sera long, puis d’enlever ce message une fois l’exécution terminée. Pour afficher un message à l’écran, un simple appel de ma fonction PHP echo suffit. Mais pour enlever le message une fois l’exécution terminée, il faut utiliser une code JavaScript. Il faut aussi que le message ait été inséré dans un conteneur (<p>, <div>, <span>,<td>, etc.) identifiable par son ID.

Cette méthode, légère et très facile à implémenter, a cependant deux inconvénients. D’une part, le navigateur pourrait arrêter le chargement de la page faute de recevoir de nouvelles informations (timeout) ; il faut donc envoyer de nouvelles données de temps en temps au navigateur, pendant l’exécution du script. D’autre part, l’utilisateur n’étant pas informé de la progression de l’opération, il risque de se lasser ; afficher une barre de progression ou un pourcentage régulièrement mis à jour n’est donc pas un luxe. Pour mettre en place un tel système, il faut d’abord créer un conteneur (<div> par exemple) au début de l’exécution du script. Il faut ensuite créer une fonction PHP mettant à jour l’affichage. Il ne restera plus qu’à appeler cette fonction pendant l’exécution du script. Finalement, on pourra rajouter à la fin du script une ligne de code JavaScript pour effacer l’affichage de la barre de progression.

<h2> Premier exemple : affichage d’un message d’attente
</h2>

La première étape consiste à créer un conteneur pour afficher le message. Le code suivant produit un tel conteneur :

1. <div id=»message» style=»display:none;»>Veuillez patienter s.v.p.</div>

Ecrire display:none dans la balise style permet de masquer le message : il devra être activé explicitement au début de l’exécution du script PHP. Pour activer le message d’attente au début du script PHP, on pourra écrire les lignes suivantes :

1. echo «<script>»;

2. echo «document.getElementById(‘message’).style.display = "block";»;

3. echo «</script>»;

4. ob_flush();

5. flush();

6. ob_flush();

7. flush();

Les lignes 1 à 3 ordonnent à PHP, par la commande echo, d’afficher un code source HTML au navigateur. Ce code (lignes 1 et 3) est composé d’une balise d’ouverture (ligne 1) et d’une balise de fermeture (ligne 3) indiquant au navigateur que le code entre ces balises est un JavaScript. Le code JavaScript est ici à la ligne 2 : il s’agit d’identifier le conteneur du message d’attente grâce à son ID et de l’afficher à l’écran (display:block permet d’afficher le message là où display:none le masque).

Mais si les commandes echo envoient des données au navigateur pour qu’ils les affiche, l’affichage dépend en réalité du navigateur. C’est lui qui décide quand et comment doivent être affichées les données à l’écran. De manière très classique, les navigateurs attendent d’avoir assez de données avant de les imprimer à l’écran. Ils attendront donc, la plupart du temps, la fin de l’exécution du code PHP ! Il faut les forcer à afficher les informations immédiatement. C’est ce que font les fonctions ob_flush() et flush().

Les méthodes ob_flush() et flush() forcent le navigateur à afficher à l’écran le contenu de leur mémoire tampon, même s’il estime que les données ne sont pas encore prêtes à être affichées. Cependant, la plupart des navigateurs sont vraiment récalcitrants et refuseront d’afficher les données immédiatement. Il faut donc leur forcer la main. Pour cela, on appellera plusieurs fois de suite les méthodes ob_flush() et flush() (dans cet ordre).

A la fin de l’exécution du script, il faut masquer le message d’attente. L’utilisateur comprendra que le script est terminé. Pour ce faire, on écrira le code suivant :

1. echo «<script>»;

2. echo «document.getElementById(‘message’).style.display = "none";»;

3. echo «</script>»;

Si l’affichage final n’a pas lieu immédiatement, c’est-à-dire si le traitement du script PHP continue, il faudra de nouveau appeler les méthodes ob_flush() et flush().

<h2> Second exemple : barre de progression
</h2>

Le premier exemple affiche un message au début de l’exécution du script et l’efface à la fin. Cependant, il ne le met pas à jour pendant l’exécution. Ce second exemple, plus complet, consiste à créer une barre de progression (sans image externe), régulièrement mise à jour. Pour utiliser la barre de progression, les instructions du script PHP qui prennent beaucoup de temps doivent être issues d’une boucle for ou while.

Un exemple de mise en oeuvre de la barre de progression décrite ci-dessous est disponible [sur cette page], vous pouvez aussi afficher [le code source] de l’exemple.

La première étape consiste à créer la barre de progression. Voici le code HTML à écrire :

1. <div id=»conteneur» style=»display:none; background-color:transparent;»>

2. <div id=»barre» style=»display:block; background-color:#CCCCCC; width:0%;»>

3. <div id=»pourcentage» style=»text-align:right;»>

4. &nbsp;

5. </div>

6. </div>

7. </div>

Trois conteneurs <div> sont créés. Le premier conteneur (ligne 1) contient l’ensemble barre de progression et étiquette contenant le pourcentage. Le deuxième conteneur (ligne 2) contiendra la barre de progression : pour l’instant sa taille est de 0% ; elle augmentera au fur et à mesure de l’exécution dus cript. La couleur de la barre de progression est à définir grâce à la propriété background-color de ce conteneur. Ici, la barre de progression sera gris clair. Le troisième conteneur (ligne 3) contiendra le pourcentage, qui sera ici affiché à l’extrémité de la barre de progression.

La seconde étape consiste à créer une fonction PHP qui permet de mettre à jour l’affichage du <div>. Dans cet exemple, nous allons simuler le remplissage d’une barre de compression en changeant la couleur du fond d’écran du conteneur. Nous changerons également le contenu du <div> pour afficher un pourcentage. Voici la fonction :

1. function progression($indice)

2. { echo «<script>»;

3. echo «document.getElementById(‘pourcentage’).innerHTML=’».$indice.»%’;»;

4. echo «document.getElementById(‘barre’).style.width=’».$indice.»%’;»;

5. echo «</script>»;

6. ob_flush();

7. flush();

8. ob_flush();

9. flush();

10. }

Il faut ensuite activer l’affichage du conteneur principal (conteneur) au début du script PHP, comme dans le premier exemple :

1. echo «<script>»;

2. echo «document.getElementById(‘conteneur’).style.display = "block";»;

3. echo «</script>»;

4. ob_flush();

5. flush();

6. ob_flush();

7. flush();

Il reste à placer l’appel à la fonction PHP progression() dans le code correspondant à la longue opération que l’on entend exécuter. Voici un exemple :

1. for( $i=0 ; $i < $x ; $i++ )

2. { $indice = ( ($i+1)100 ) / $x;

3. progression($indice);

4.

5. /
Placez ici le code très très long … /

6. /
Exemple : */

7. for( $j = 0 ; $j < 10000 ; $j++ )

8. echo ‘.’;

9. }

A la ligne 1, on crée une boucle for qui s’exécutera x fois (en fonction du contenu de la variable $x, par hypothèse définie plus haut dans le code). On calcule ensuite le pourcentage de progression (ligne 2) et on appelle la fonction progression() avec ce pourcentage en paramètre. Un traitement très long est simulé aux ligne 5 à 8.

Il reste à enlever la barre de progression, une fois que le traitement est terminé, comme dans le premier exemple. On peut aussi laisser la barre à l’écran et remplacer 100% par un message :

1. echo «<script>»;

2. echo «document.getElementById(‘pourcentage’).innerHTML = "TERMINÉ !";»;

3. echo «</script>»;

Cette méthode d’affichage d’une barre de progression est mise en œuvre dans le script Valhalla NatTest que vous pouvez utiliser en guise de démonstration (choisissez une plage d’au moins 20 ports en TCP).

Un exemple de mise en oeuvre de la barre de progression décrite ci-dessus est disponible sur cette page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</title>
</head>

<div id="conteneur" style="display:none; background-color:transparent; position:absolute; top:100px; left:5%; margin-right:5%; height:50px; width:90%; border:1px solid #000000;">
	<div id="barre" style="display:block; background-color:#CCCCCC; width:0%; height:100%;">
		<div id="pourcentage" style="text-align:right; height:100%; font-size:1.8em;">
			&nbsp;
		</div>
	</div>
</div>

<body>
	
<p>Barre de progression PHP : Demonstration</p>
	
<?php

echo "<script>";
	echo "document.getElementById('conteneur').style.display = \"block\";";
echo "</script>";
ob_flush();
flush();
ob_flush();
flush();

$x = 1000;

for( $i=0 ; $i < $x ; $i++ )
{ 
	$indice = round(( ($i+1)*100 ) / $x);
	progression($indice);

	/* Placez ici le code tres tres long a executer … */
	/* Exemple : */
	for( $j = 0 ; $j < 1000 ; $j++ )
		echo ".";
	echo '<br />';
}


echo "<script>";
	echo "document.getElementById('pourcentage').innerHTML='TERMINE !';";
echo "</script>";



permalink:  /2007/08/01/php-wait-screen/
//----
function progression($indice)
{	
	echo "<script>";
		echo "document.getElementById('pourcentage').innerHTML='$indice%';";
		echo "document.getElementById('barre').style.width='$indice%';";
	echo "</script>";
	ob_flush();
	flush();
	ob_flush();
	flush();
}
?>
</body>
</html>