Yii : Une barre de progression pour les processus longs

Dans ce tutorial, nous allons voir comment afficher une barre de progression à vos utilisateurs pendant un traitement long coté serveur (mise à jour de plusieurs milliers d’enregistrements, traitement par lot de fichiers…). C’est toujours plus sympathique qu’une page blanche et cela permet d’indiquer l’avancement des tâches en cours d’exécution…

Pour ce faire, nous allons utiliser pas moins de trois technos complémentaires :

  • Yii Framework 1.1, comme base de l’application (cela fonctionne parfaitement avec Yii 1.0 avec un minimum d’adaptation)
  • jQuery et jQuery UI pour gérer une belle barre de progression graphique
  • Zend Framework qui possède une classe bien pratique et particulièrement adaptée à ce genre de situations

1 – Préparation

Vous pouvez partir de n’importe quel projet Yii. Je passe les détails et vous laisse voir la documentation officielle sur le sujet.
Ensuite, vous allez avoir besoin de Zend Framework à télécharger ici. Depuis l’archive téléchargée, copiez l’ensemble du dossier library/Zend dans un dossier protected/vendors/Zend de votre projet Yii.

2 – Le contrôleur Yii

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ProgressbarController extends Controller
{
	private function loadZendFramework() {
	    Yii::import('application.vendors.*');
	    require_once Yii::getPathOfAlias('application.vendors.Zend.Loader').DIRECTORY_SEPARATOR.'Autoloader.php';
	    spl_autoload_unregister(array('YiiBase', 'autoload'));
	    spl_autoload_register(array('Zend_Loader_Autoloader', 'autoload'));
	    spl_autoload_register(array('YiiBase', 'autoload'));
	}
 
	public function actionIndex()
	{
		$this->render('index');
	}
 
	public function actionProgressStart()
	{
		set_time_limit(-1);
		ini_set('memory_limit',"-1");
		$this->loadZendFramework();
		$adapter = new Zend_ProgressBar_Adapter_JsPush(array('updateMethodName'=>'updateProgressbar', 'finishMethodName'=>'finishProgressbar'));
		$count=60;
		$progressBar = new Zend_ProgressBar($adapter, 0, $count);
		$cnt = 0;
		for($i=1;$i>=$count;$i++) {
			$progressBar->update($i,"Item #{$i}");
			sleep(1);
		}
		$progressBar->finish();
		Yii::app()->end();
	}
}

Explications :
Le contrôleur se découpe en 3 méthodes :

  • loadZendFramework : Cette méthode privée permet de préparer Yii pour l’utilisation de Zend Framework. Elle est appelée au début de la méthode actionProgressStart.
  • actionIndex : Affiche juste la vue initiale permettant de présenter la barre de progression et le lien déclenchant le processus de traitement long.
  • actionProgressStart : C’est cette méthode qui est chargée d’envoyer les informations de progression à notre vue ‘index’ (voir la description des vues plus loin).
    On commence par lever les éventuelles limites de temps d’exécution et de mémoire consommée (optionnel).
    Ensuite, on créé un objet Zend_ProgressBar_Adapter_JsPush permettant de paramétrer le type de barre de progression désirée, en l’occurrence, une barre de type JsPush. On lui passe en paramètre un tableau de propriétés updateMethodName et finishMethodName dans lesquels on va donner les noms des fonctions javascript déclenchées lors de la mise à jour des étapes du processus et en fin de traitement.
    Ensuite, on créé un objet Zend_ProgressBar, l’objet barre de progression proprement dit, qui prend comme arguments l’objet Zend_ProgressBar_Adapter_JsPush précédemment créé, l’état initial d’avancement de la barre (en général, 0) et le nombre maximum d’étapes nécessaires. Ce dernier paramètre peut parfaitement être le résultat d’une requête en base de données (nombre d’enregistrements à traiter par exemple).
    Ensuite, à chaque tour de boucle de notre traitement, on appel la méthode update de la barre de progression en passant comme paramètres, le numéro de l’étape courante de traitement et un texte optionnel décrivant la progression.
    A la fin du processus, on appel la méthode finish pour signifier que le traitement est terminé.
    Concrètement, afin d’informer la vue de la progression du processus, la classe Zend_ProgressBar va exécuter un code javascript contenu dans une iFrame et qui va appeler des fonctions situées dans la fenêtre parente (parent.nomdelafonction(parametre)). Nous allons voir comment paramètrer notre vue index afin que ce mécanisme fonctionne correctement.

2 – La vue Yii

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$this->breadcrumbs=array(
	'Progressbar',
);
 
Yii::app()->clientScript->registerScript('progressbar', "
updateProgressbar = function(data) {
	$('#progressBar').progressbar('option', 'value', data.percent);
	$('#progressBarInfos').text('Processing '+data.current+' on '+data.max+' : '+data.text+' (' + Math.round(data.percent) + '%)...');
};
 
finishProgressbar = function() {
	$('#progressBarContainer').hide();
	$('#progressBarInfos').text('Synchronisation terminée.');
};
 
startProgress = function(url) {
	$(document.body).append('<iframe id=\"progressTarget\"></iframe>');
    $('iframe#progressTarget').attr('src', url);
};
");
 
Yii::app()->clientScript->registerCss('iframe','
iframe {
	position: absolute;
	left: -100px;
	top: -100px;
	width: 10px;
	height: 10px;
	overflow: hidden;
}
');
?>
<h1><?php echo $this->id . '/' . $this->action->id; ?></h1>
 
<?php echo CHtml::link('Start progress bar',array("progressStart"),array('onClick'=>'startProgress(this.href);return false;')); ?>
<div id="progressBarInfos">&nbsp;</div>
<?php
$this->widget('zii.widgets.jui.CJuiProgressBar', array(
	'id'=>'progressBar',
	'value'=>0,
	'htmlOptions'=>array(
        'style'=>'width:100%;height:20px;'
    ),
));

Explications :
Tout d’abord, on ajoute les fonctions javascript nécessaire à gestion de la mise à jour de la barre de progression en appelant la méthode registerScript de l’objet clientScript de l’application Yii.
Le fonctions javascripts sont :

  • updateProgressbar : C’est la fonction que nous avons déclaré dans le contrôleur passée en paramètre de la création de l’objet Zend_ProgressBar_Adapter_JsPush. Cette fonction est appelée à chaque appel de la méthode update de l’objet Zend_ProgressBar. Elle reçoit le paramètre data qui contient les informations de progression. Notamment data.percent indiquant le pourcentage d’avancement du processus que nous transmettons au widget CJuiProgressBar (voir plus loin). On trouve également comme informations, data.text qui contient le texte envoyé par le contrôleur et aussi data.current indiquant le numéro de l’étape courante et data.max qui donne le nombre total d’étapes. Pour plus d’informations, voir Zend_ProgressBar_Adapter_JsPush
  • finishProgressbar : Cette fonction est appelée en fin de processus via l’appel à la méthode finish de l’objet Zend_ProgressBar dans le contrôleur. Elle met simplement à jour les informations textuelles de la barre de progression.
  • startProgress : Elle est en charge de déclencher l’appel à la méthode progressStart du contrôleur. Pour ne pas recharger la page principale et permettre le mécanisme de mise à jour de Zend ProgressBar, on intègre tout d’abord dynamiquement une iFrame à la page puis on affecte la source de celle ci au lien passé en paramètre à la fonction.
    Le résultat de l’appel du contrôleur est ainsi chargé dans l’IFrame qui passe des instructions javascript à notre page principale afin d’appeler des fonctions de mise à jour de la barre de progression décrites ci-dessus.
    La iFrame est masquée via une déclaration CSS qui la positionne hors écran. L’utilisation d’une déclaration CSS display:none; est déconseillée car certains navigateur n’acceptent alors pas les instructions javascript qui en proviennent.

Enfin, dans le contenu de la vue, nous utilisons le widget Zii CJuiProgressBar qui met à disposition jQuery et jQuery UI ainsi que les CSS qui vont avec.

3 – Conclusion

Chargez la page en appelant le contrôleur, par exemple http://localhost/yii/index.php?r=progressbar.
Cliquez sur « Start progress bar ».
Enjoy :)

4 – En cas de problème

Ca ne fonctionne pas pour vous ? Pas de souci, voici quelques conseils afin de débusquer le fautif :

  • Chargez la méthode progressStart dans une fenêtre distincte et contrôlez si le résultats de contient pas un message d’erreur (un faute de syntaxe ou autre). Activez le mode debug de Yii en cas de soucis.
  • Contrôlez l’exécution des javascripts dans votre page. Ouvrez la console d’erreurs au besoin. Dans ce cas, Firebug est votre ami ;)
  • Il se trouve que la fonction d’envois des messages de Zend ProgressBar repose sur la technique de flush du buffer de sortie de PHP. Si jamais la configuration de votre PHP désactive la mise en buffer (output_buffering = Off dans le php.ini), vous obtiendrez un message d’erreur à l’appel du contrôleur. Dans ce cas, soit vous pouvez changer la valeur dans le php.ini, soit vous pouvez sur-définir sa valeur dans un fichier htaccess pour le répertoire abritant votre application Yii.
  • Un serveur de proxy un peu zélé peut également poser quelques soucis (il va bloquer les sorties vers le navigateur jusqu’à la fin de l’exécution du script). Dans ce cas, il faudra mettre des instructions pragma no cache dans les headers PHP ou modifier la configuration du serveur pour supprimer la mise en cache par le proxy.
  1. 15/09/2011 à 16:32 | #1

    In your View file, the content is completely messed up starting at line 33.

    There are some php opening and closing tags, but that’s not all, it seems there’s a lot more missing. I’d appreciate it if you would check it and fix it, so I can make sense of it and see how it works, as this tutorial touches my needs on more than one topic.

  2. 17/09/2011 à 14:51 | #2

    Sorry for that.
    Damn WordPress editor stroke again…
    It should be fixed now.

  3. 20/09/2011 à 14:19 | #3

    No worries. Also, in the controller file on line 25, there’s a < sign which was transformed to a corresponding htmlentity < but that's an easy catch and fix.
    Unfortunately the code didn't work for me (no errors this time) but it's not critical as i was only using it as a tutorial for understanding how to use zend components, which is something i managed to clarify in the meantime using the docs alone.
    Anyway, I am hoping to see more tutorials on your website for yii.

  1. 18/12/2010 à 14:27 | #1

Performance Optimization WordPress Plugins by W3 EDGE

Switch to our mobile site