Symfony Process : Comment exécuter du shell dans son code

Symfony Process : Comment exécuter du shell dans son code

  • Post comments:2 commentaires
  • Reading time:3 mins read

Symfony Process permet d’exécuter des scripts python, shell, exécuter des commandes etc… Ce qui permet d’ajouter une corde à votre arc lorsque vous développez votre application web.

Installation

Pour installer le composant Process rien de plus simple :

composer require symfony/process

Utilisation

Pour cet article on va prendre exemple, un script shell qui me permet d’afficher “Hello World” sur mon terminal (l’idée est géniale, j’en ai conscience). Ce script shell il est dans mon dossier public/helloworld.sh (avec les droits d’exécution).

// public/helloworld.sh
#!/bin/bash

echo "Hello World"

Utilisons le composant dans un controller :

// src/Controller/HelloWorldController.php
namespace App\Controller;

// ...
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

class HelloWorldController extends AbstractController
{
    public function hello(Request $request)
    {
        $pathToHelloWorldScript = "/.../myproject/public/helloworld.sh";
        $process = new Process(['sh', $pathToHelloWorldScript]);
        $process->run();

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        // Cela affichera "Hello World"
        echo $process->getOutput();

        // ...
    }
}

Lorsqu’on utilise le composant Process, il va créer un sous processus qui va lancer votre commande, ce sous processus va être autonome mais sans la fonction isSuccessful(), la fonction getOutput() aurait retourner null ou notre valeur.

Utilisons maintenant dans une commande, désormais le script, on va lui ajouter quelques détails qu’il permettra de trier le retour de la commande.

Info : Un article est disponible concernant les commandes : Lien

// src/Command/HelloCommand.php
namespace App\Command;

// ...
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

class HelloCommand extends Command
{
    protected static $defaultName = "app:hello";

    protected function configure()
    {
        //...
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    { 
        $process = new Process(['ls', '-lsa']);
        $process->start();

        foreach ($process as $type => $data) {
            if ($process::OUT === $type) {
                echo "\nDebug :".$data;
            } else { // $process::ERR === $type
                echo "\nErreur : ".$data;
            }
        }
        // Depuis Symfony 5.2, vous pouvez utiliser directement la variable Command::SUCCESS
        return 0;
    }
}

En utilisant cette fonction $process->start(), cela lancera en tâche de fond la commande que vous avez faite (pour ma part ls -la) et poursuivre votre commande.

Tant que le process sera en runtime (actif), il restera dans le foreach. Il affichera les retours de votre process avec un tri par le type ($type).

Poussons le vice

Désormais, nous allons plus loin. Notre composant Process a une fonction qui s’appelle waitUntil, une fonction qui peut exécuter une fonction anonyme qui permet de rester dans le process tant qu’il n’est pas true.

Nous allons crée un nouveau script bash simple comme ci-dessous :

// public/helloworld.sh
#!/bin/bash

echo "Begin Processus"

i=0

while [ $i -le 30 ]
do
  let "i+=1"
done

echo "The Script is finish."
// src/Controller/HelloWorldController.php
namespace App\Controller;

// ...
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

class HelloWorldController extends AbstractController
{
    public function hello(Request $request)
    {
        $pathToHelloWorldScript = "/.../myproject/public/helloworld.sh";
        $process = new Process(['sh', $pathToHelloWorldScript]);
        $process->start();

        $process->waitUntil(function ($type, $output) {
            // Tant que ce n'est pas true, je reste dans cette fonction anonyme.
            return $output === 'The Script is finish.';
        });
        
        // ...
    }
}

Cependant, si on voudrait stop le process, une fonction le permet : $process->stop()

// src/Command/HelloCommand.php
namespace App\Command;

// ...
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

class HelloCommand extends Command
{
    protected static $defaultName = "app:hello";

    protected function configure()
    {
        //...
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    { 
        $process = new Process(['ls', '-lsa']);
        $process->start();

        // ... 
        $process->stop(3, SIGINT);
        
        return 0;
    }
}

Désormais, vous pouvez exécuter des sous process avec le composant Process de Symfony. Cela dépanne bien lorsqu’il faut automatiser avec des scripts pythons ou autre.

N’hésitez pas à suivre la documentation officielle de Symfony, pour plus d’information.

You need to trust the Symfony Process

Cet article a 2 commentaires

  1. Thomas

    Bonjour,
    Merci pour cet article !
    Une question, est-ce possible de lancer par exemple une commande Symfony personnalisée, disons le traitement de l’envoi d’une file d’attente d’email (process qui peut être très long) depuis l’interface web de notre site (utilisateur apache donc), tout en ayant les contraintes de mémoire et de timeout du php.ini de la cli ?
    L’idée serait qu’un utilisateur web puisse lancer une commande longue en arrière-plan, et que celle-ci rende la main en continuant sa tâche jusqu’à ce qu’elle soit finie.

    1. Gary Houbre

      Bonjour Thomas,

      Oui tout à fait comme le dernier exemple, vous pouvez mettre $process->start() sans utiliser la fonction waitUntil() (qui lui permet de faire rester dans votre process) et laisser le process faire sa vie.

      J’espère avoir répondu à ta question 😀

Laisser un commentaire