phpsymfonyreal-timesymfony-process

How to execute a command within a controller of a Symfony2 application and print in real-time the output in a Twig template


I need to execute a long-lasting command within a controller of my Symfony2 application and to return to the user in real time the output of the terminal.

I have read this:

http://symfony.com/doc/current/components/process.html#getting-real-time-process-output

I can't figure out how to print in real time the terminal output in a Twig template.

EDIT: Thanks to the Matteo's code and users comments, the final implementation is:

/**
 * @Route("/genera-xxx-r", name="commission_generate_r_xxx")
 * @Method({"GET"})
 */
public function generateRXXXsAction()
{
    //remove time constraints if your script last very long
    set_time_limit(0);        

    $rFolderPath = $this->container->getParameter('xxx_settings')['r_setting_folder_path'];
    $script = 'R --slave -f ' . $rFolderPath . 'main.R';

    $response = new StreamedResponse();
    $process = new Process($script);
    $response->setCallback(function() use ($process) {
        $process->run(function ($type, $buffer) {
            //if you don't want to render a template, please refer to the @Matteo's reply
            echo $this->renderView('AppBundle:Commission:_process.html.twig',
                array(
                    'type' => $type,
                    'buffer' => $buffer
                ));
            //according to @Ilmari Karonen a flush call could fix some buffering issues
            flush();
        });
    });
    $response->setStatusCode(200);
    return $response;
}

Solution

  • If you need lo launch a simple shell script and capture the output, you can use a StreamedResponse in conjunction with the Process callback you posted.

    As Example, suppose you have a very simple bash script like follow:

    loop.sh

    for i in {1..500}
    do
       echo "Welcome $i times"
    done
    

    You can implement your action like:

    /**
     * @Route("/process", name="_processaction")
     */
    public function processAction()
    {
        // If your script take a very long time:
        // set_time_limit(0);
        $script='/path-script/.../loop.sh';
        $process = new Process($script);
    
        $response->setCallback(function() use ($process) {
            $process->run(function ($type, $buffer) {
                if (Process::ERR === $type) {
                    echo 'ERR > '.$buffer;
                } else {
                    echo 'OUT > '.$buffer;
                    echo '<br>';
                }
            });
        });
        $response->setStatusCode(200);
        return $response;
    }
    

    And depends on the buffer length you can have an output like:

    .....
    OUT > Welcome 40 times Welcome 41 times 
    OUT > Welcome 42 times Welcome 43 times 
    OUT > Welcome 44 times Welcome 45 times 
    OUT > Welcome 46 times Welcome 47 times 
    OUT > Welcome 48 times 
    OUT > Welcome 49 times Welcome 50 times 
    OUT > Welcome 51 times Welcome 52 times 
    OUT > Welcome 53 times 
    .....
    

    You can wrap this in a portion of a page with a render controller as example:

    <div id="process">
        {{ render(controller(
            'AcmeDemoBundle:Test:processAction'
        )) }}
    </div>
    

    More info here

    Hope this help