Comment faire une barre de progression dans une commande Laravel

Il y a quelque temps, je vous ai parlé de Laravel et pourquoi j’utilise de plus en plus ce framework PHP. En effet, il est par exemple très facile de créer des scripts et de les lancer avec PHP CLI en ligne de commande. D’ailleurs, si vous n’avez jamais utilisé PHP en mode console, je vous invite à aller voir ce tutoriel qui utilise bien évidemment Laravel pour vous faciliter la vie.

Maintenant, que vous avez créé plein de supers scripts avec les commandes de Laravel, vous voulez peut-être commencer à améliorer tout ça. Personnellement, j’ai eu le « problème » lorsque mes scripts ont commencé à traiter de gros volumes de données. C’était notamment le cas sur une table qui avait plusieurs millions de lignes et qu’il fallait exporter. Le script fonctionnait très bien c’était pas le souci, mais j’avais besoin de visibilité sur ce qu’il se passait. C’est quand même mieux qu’une sortie vide ou avec des messages de debug pas très sexy.

Pour ce qui va suivre dans ce tutoriel et faire une barre de progression qui se remplit en temps réel, il vous faut un objectif mesurable. Par exemple, avec l’histoire de ma table que j’exportais au-dessus, il fallait que compte d’abord le nombre de ligne avant de passer à la suite. Quand je devais parser un nombre de fichiers dans un dossier, je comptais le nombre fichier. C’est logique, mais je préfère le préciser pour ceux qui débutent et qui voudraient coller des barres de progressions partout. Maintenant, nous allons pouvoir passer aux choses sérieuses avec des exemples très simples. Encore une fois, j’ai utilisé le framework Laravel pour créer et lancer mes commandes de test et d’exemple.

Créer une barre de progression avec la classe Symfony ProgressBar

Pour commencer, comme d’habitude une fois dans notre projet Laravel, nous allons créer une commande de test. Il suffit de lancer la commande suivante :

php artisan make:command ExempleProgressBarSymfonyComponent

Vous pouvez bien sûr appeler votre commande comme vous le souhaitez ou directement intégrer l’exemple du contenu qui va suivre dans votre script. Une fois cette commande crée, j’ajoute mon code qui est le suivant :

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Helper\ProgressBar;

class ExempleProgressBarSymfonyComponent extends Command
{
    protected $signature = 'progressbar:symfony';
    protected $description = "Exemple d'une progress bar avec le component Symfony";

    public function handle()
    {
        $etape = rand(1,100);
        $progress = new ProgressBar($this->output, $etape);
        $progress->start();

        for ($i = 1; $i <= $etape; $i++) {
            // Le code de votre script qui tourne dans une boucle
            // ...
            // La barre de progression avance
            $progress->advance();
            // Simulation d'un traitement qui prend x temps
            $time = rand(0, 2);
            sleep($time);
        }
        // La progression de la barre est terminée
        $progress->finish();
        return Command::SUCCESS;
    }
}

Si je lance plusieurs fois ce script avec ma nouvelle commande à l’aide de :

php artisan progressbar:symfony

J’obtiens alors les résultats suivants :

27/27 [============================] 100%
12/12 [============================] 100%

Pendant le traitement, vous devriez avoir quelque chose qui ressemble à ça :

20/57 [=========>------------------]  35%

C’est quand même plus sympathique à l’œil que des messages de debug à l’ancienne avec des simples echo qui s’enchaînent à la ligne non ? Il y a plusieurs choses à dire sur cette commande et son contenu :

  • use Symfony\Component\Console\Helper\ProgressBar; nous permet la classe ProgressBar de Symfony
  • Le nombre d’étapes est un nombre aléatoire entre 1 et 100 pour coller à toutes les situations et voir que ça fonctionne bien
  • Le sleep permet de simuler un traitement qui prendrait plus ou moins de temps, il ne vaut mieux pas le garder de votre côté pour la suite de vos aventures

Comment faire une progress bar en PHP seulement avec des echo() dans une commande

Bon en fait pour être tout à fait honnête, j’aime bien les echo(). Le script est un peu moins élégant, mais au moins, on ne va pas chercher une classe externe, et ça, on aime. Avec ce tutoriel, vous allez avoir le choix comme ça. Le problème, c’est que cela pose d’autres soucis, surtout si vous avez un petit terminal qui n’affiche pas beaucoup de caractères sur la largeur.

J’ai créé une nouvelle commande pour garder l’ancienne de côté. Commençons par la première version de notre nouvelle barre de progression avec seulement des echo sans la classe Symfony ProgressBar :

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class ExempleProgressBarOnlyEcho extends Command
{
    protected $signature = "progressbar:echo";
    protected $description = "Exemple d'une progress bar avec seulement des echo";

    public function handle()
    {
        // ...
        // Le nombre d'étapes calculé plus tôt dans le script
        // Ici un nombre aléatoire pour l'exemple
        $etape = rand(1,100);
        // On commence à 0
        $count=0;
        // Début de la progress bar
        echo "Progression : 0% [";

        // Lancement du script (for, foreach, etc)
        for ($i = 1; $i <= $etape; $i++) {
            // Votre traitement
            // ...
            // Calcul de l'avancement de la progression
            $count++;
            $percent = ($count / $etape) * 100;
            $percent = number_format($percent, 2);
            $status = "$percent% ($count/$etape)";
            $bar = str_pad('', $percent, '=');
            echo "\r[$bar] $status";
            // On simule une durée de traitement
            $time = rand(0,2);
            sleep($time);
        }
        echo "]";
        // Écriture d'une nouvelle ligne pour la suite
        echo "\n";
        return Command::SUCCESS;
    }
}

Lorsque l’on appelle cette commande, on obtient alors les résultats suivants si on la lance plusieurs fois :

[================================================] 100.00% (59/59)]
[================================================] 100.00% (23/23)]
[================================================] 100.00% (42/42)]

C’est ce que l’on veut, tout semble parfaitement fonctionner. Par contre si je réduis volontairement la taille de mon terminal, j’ai alors quelque chose qui ressemble à ça :

Le terminal est trop petit ?

Ce n’est pas très esthétique non ? On va résoudre ça avec une nouvelle version de la barre de progression :

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class ExempleProgressBarOnlyEcho extends Command
{
    protected $signature = "progressbar:echo";
    protected $description = "Exemple d'une progress bar avec seulement des echo";

    public function handle()
    {
        $etape = rand(1,100);
        $count=0;
        // Récupération de la taille du terminal
        $width = intval(shell_exec('tput cols'));
        echo "Progression : 0% [";
        for ($i = 1; $i <= $etape; $i++) {
            // Votre traitement
            // ...
            $count++;
            $percent = ($count / $etape) * 100;
            $percent = number_format($percent, 2);
            $status = "$percent% ($count/$etape)";
            $barLength = min(100, $width - strlen($status) - 3);
            $bar = str_pad('', $barLength, '=');
            echo "\r[$bar] $status";
            sleep(rand(0,2));
        }
        echo "]";
        echo "\n";
        return Command::SUCCESS;
    }
}

Il y a plusieurs choses à dire sur cette nouvelle version du script avec cette nouvelle barre de progression :

  • La ligne : $width = intval(shell_exec(‘tput cols’)); permet de récupérer la largeur du terminal pour gérer notre affichage
  • $barLength = min(100, $width – strlen($status) – 3); nous permet de délimiter la largeur maximale de la barre de progression
  • $bar = str_pad( », $barLength, ‘=’); permet de limiter l’affichage de la barre de progression

On peut dire qu’après tout ça, on s’est fait notre propre barre de progression en responsive design dans un terminal. Pas mal non ? Le résultat est alors le suivant :

3 réflexions au sujet de “Comment faire une barre de progression dans une commande Laravel”

Laisser un commentaire