8

I want to asynchronously call a Command from within a Controller in Symfony2.

So far i found the following solution:

$cmd = $this->get('kernel')->getRootDir().'/console '.(new MLCJobWorkerCommand)->getName().' '.$job->getId().' 2>&1 > /dev/null';
$process = new Process($cmd);
$process->start();

Is there a better way to accomplish this?

Edit:

I need the Process to run in background and the Controller to return right after it started the former. I tried:

$cmd = $this->get('kernel')->getRootDir().'/console '
     .(new MLCJobWorkerCommand)->getName()
     .' '.$job->getId().' 2>&1 > /dev/null & echo \$!';
$process = new Process($cmd);
$process->mustRun();
$params["processid"] = $process->getOutput();

but the Controller doesn't return a Response until the Process has finished.

dmb
  • 247
  • 5
  • 15
  • 3
    The problem is that the new process will stop as soon as the parent process is stopped as well, which is a limitation in PHP. A better solution might be to use a queueing system like RabbitMQ. – Gerry Apr 13 '15 at 09:14
  • This looks great, I shall give it a try. In the past I used to start a background process in PHP in the manner indicated by my edit but the Controller seems to block here. Is this related to your comment? – dmb Apr 13 '15 at 10:08

2 Answers2

5

I agree with Gerry that if you want to be "asynchronously" then you selected not the best way

I can recommend an alternative of RabbitMQ: JMSJobBundle
http://jmsyst.com/bundles/JMSJobQueueBundle/master/installation

Where you can create a queue of you console commands something like:

class HomeController ... {
    // inject service here
    private $cronJobHelper;
    // inject EM here
    private $em;

    public function indexAction() {
        $job = $this->cronJobHelper->createConsoleJob('myapp:my-command-name', $event->getId(), 10);
        $this->em->persist($job);
        $this->em->persist($job);
        $this->em->flush();
    }
}


use JMS\JobQueueBundle\Entity\Job;

class CronJobHelper{

    public function createConsoleJob($consoleFunction, $params, $delayToRunInSeconds, $priority = Job::PRIORITY_DEFAULT, $queue = Job::DEFAULT_QUEUE){
        if(!is_array($params)){
            $params = [$params];
        }

        $job = new Job($consoleFunction, $params, 1, $queue, $priority);
        $date = $job->getExecuteAfter();
        $date = new \DateTime('now');
        $date->setTimezone(new \DateTimeZone('UTC')); //just in case
        $date->add(new \DateInterval('PT'.$delayToRunInSeconds.'S')); 
        $job->setExecuteAfter($date);

        return $job;
    }
}
gam6itko
  • 15,128
  • 2
  • 19
  • 18
Evgeniy Kuzmin
  • 2,384
  • 1
  • 19
  • 24
0

Checkout AsyncServiceCallBundle, it allows you to call your service's methods completely asynchronously using this approach. The process responsible for current request handling doesn't wait for its child to be finished.

Everything you need is to call it like this:

$pid = $this->get('krlove.async')->call('service_id', 'method', [$argument1, $argument2]);
Andrii Mishchenko
  • 2,626
  • 21
  • 19