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.
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;
}
}
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]);
Related
I developed an API in php with washable to run an algorithm in C ++ with opencv. The application sends a photo to the server, which in turn starts an .exe file responsible for processing this image. When executing the file via terminal, the algorithm correctly processes the image. However, to perform this processing via code in the API I am using Symfony, more precisely Process. Well, when executing the line below it always returns to me that the .exe file was not found.
$process = new Process(['feridas/exec', 'final.png']);
I have also tried in the following ways, but without success.
$process = new Process(['./exec', 'final.png']);
$process = new Process(['server-laravel/resources/feridas/exec']);
Code complete:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Process\Process;
class ClassificationController extends Controller
{
public function process(Request $request)
{
/** #var $image UploadedFile*/
$image = $request->files->get('image');
if (empty($image)) {
throw new \Exception("Arquivo não fornecido.");
}
$validExtensions = ['image/jpeg', 'image/png', 'image/jpg'];
if (!in_array($image->getMimeType(), $validExtensions)) {
dd($image->getMimeType());
throw new \Exception("Arquivo não suportado");
}
if (!$image->move(resource_path('feridas'), $image->getClientOriginalName())) {
throw new \Exception("Error moving file.");
}
$newPath = resource_path('feridas/' . $image->getClientOriginalName());
return $this->processImage($newPath);
}
private function processImage($newPath){
$process = new Process(['ls', '-la', resource_path('feridas')]);
$process->run();
$process->wait();
if (!$process->isSuccessful()) {
return [
'success' => true, //ou false
'message' => 'Could not execute script: %s', $process->getErrorOutput()
];
throw new ProcessFailedException($process);
}
$output = $process->getOutput();
return [
'success' => true, //ou false
'message' => $output
];
}
}
outuput:
When executing the same algorithm via terminal, it correctly returns the output, time of execution of the algorithm in seconds.
If there is no cwd argument value in the Process class, getpwd() the current path of php.
Laravel is the path to the entry point, public/index.php.
var_dump(getcwd());
// /home/user/public
So it is clear to define the absolute path where the file exists.
var_dump(resource_path('feridas/exec'));
// /home/user/resources/feridas/exec
Try it.
use Symfony\Component\Process\Process;
$process = new Process([resource_path('feridas/exec'), resource_path('feridas/final.png')]);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
If that doesn't work, please let us know the next result.
$process = new Process(['ls', '-la', resource_path('feridas/exec'), resource_path('feridas/final.png')]);
$process = new Process(['ls', '-la', resource_path('feridas/exec'), $newPath]);
// SSH Terminal
[root#dev ~]# /home/user/resources/feridas/exec /home/user/resources/feridas/final.png
[root#dev ~]# ls -la /home/user/resources/feridas/exec /home/user/resources/feridas/final.png
Symfony process doc - https://symfony.com/doc/current/components/process.html#getting-real-time-process-output
Answers to the results of further execution.
Enter the directory to be moved in the second argument, $cwd.
//$process = new Process(['ls', '-la', resource_path('feridas')]);
$process = new Process(['./exec'], resource_path('feridas'));
// arguments
// $command = ['./exec']
// $cwd = resource_path('feridas')
Check the class arguments - https://github.com/symfony/process/blob/5.x/Process.php#L141
public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
I have a symfony commad that needes few minutes to be completed. And i want to run it by a http request. The problem is that the server kills the command process just after sending response, he has a timeout.
i tried to use symfony process asynchronously But still not working ( i get always the timeout problem).
I dont want use the kernel.terminate event, since it is not a best practice. Here is the code in my controller :
$commandProcess=new Process('php bin/console app:doSomeThing');
$commandProcess->setWorkingDirectory('./../');
$commandProcess->start();
$commandProcess->setTimeout(50000000);
Any response will be much appreciated.
I had to desibale the process output and add '&' after the command
Here the answer:
new Process('php bin/console app:dosomthing &');
$commandProcess=new Process('php bin/console app:dosomthing &');
$commandProcess->setWorkingDirectory('./../');
$commandProcess->disableOutput();
$commandProcess->setTimeout(1800);
$commandProcess->start();
I write here the solution that has finally worked for me
First create a service:
use Symfony\Component\Process\Process;
use Symfony\Component\HttpKernel\KernelInterface;
class ProcessService {
public function __construct(
private KernelInterface $appKernel
){ }
public function executeProcessAsync($processToExecute) {
$rootDir = $this->appKernel->getProjectDir();
$process = Process::fromShellCommandline($processToExecute . ' &');
$process->setWorkingDirectory($rootDir);
$process->disableOutput();
$process->setTimeout(0);
$process->start();
}
}
Then we use the service from a controller:
class MyController {
public function __construct(
private ProcessService $procesService
){
public function __invoke(Request $request)
{
// (other stuff ...)
$this->procesService->executeProcessAsync('php bin/console mycommands:command ');
// (other stuff...)
}
}
Symfony2 enables developers to create their own command-line commands. They can be executed from command line, but also from the controller. According to official Symfony2 documentation, it can be done like that:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
...
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
}
But in this situation we wait for the command to finish it's execution and return the return code.
How can I, from controller, execute command forking it to background without waiting for it to finish execution?
In other words what would be equivalent of
$ nohup php app/console demo:greet &
From the documentation is better use start() instead run() if you want to create a background process. The process_max_time could kill your process if you create it with run()
"Instead of using run() to execute a process, you can start() it: run() is blocking and waits for the process to finish, start() creates a background process."
According to the documentation I don't think there is such an option: http://api.symfony.com/2.1/Symfony/Component/Console/Application.html
But regarding what you are trying to achieve, I think you should use the process component instead:
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->run(function ($type, $buffer) {
if ('err' === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
And as mentioned in the documentation "if you want to be able to get some feedback in real-time, just pass an anonymous function to the run() method".
http://symfony.com/doc/master/components/process.html
Is there a way to run a console command from a Symfony 2 test case? I want to run the doctrine commands for creating and dropping schemas.
This documentation chapter explains how to run commands from different places. Mind, that using exec() for your needs is quite dirty solution...
The right way of executing console command in Symfony2 is as below:
Option one
use Symfony\Bundle\FrameworkBundle\Console\Application as App;
use Symfony\Component\Console\Tester\CommandTester;
class YourTest extends WebTestCase
{
public function setUp()
{
$kernel = $this->createKernel();
$kernel->boot();
$application = new App($kernel);
$application->add(new YourCommand());
$command = $application->find('your:command:name');
$commandTester = new CommandTester($command);
$commandTester->execute(array('command' => $command->getName()));
}
}
Option two
use Symfony\Component\Console\Input\StringInput;
use Symfony\Bundle\FrameworkBundle\Console\Application;
class YourClass extends WebTestCase
{
protected static $application;
public function setUp()
{
self::runCommand('your:command:name');
// you can also specify an environment:
// self::runCommand('your:command:name --env=test');
}
protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);
return self::getApplication()->run(new StringInput($command));
}
protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();
self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}
return self::$application;
}
}
P.S. Guys, don't shame Symfony2 with calling exec()...
The docs tell you the suggested way to do it. The example code is pasted below:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
'command' => 'demo:greet',
'name' => 'Fabien',
'--yell' => true,
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
// ...
}
Yes, if your directory structure looks like
/symfony
/app
/src
then you would run
phpunit -c app/phpunit.xml.dist
from your unit tests you can run php commands either by using
passthru("php app/console [...]") (http://php.net/manual/en/function.passthru.php)
exec("php app/console [...]") (http://www.php.net/manual/en/function.exec.php)
or by putting the command in back ticks
php app/consode [...]
If you are running the unit tests from a directory other than symofny, you'll have to adjust the relative path to the app directory for it to work.
To run it from the app:
// the document root should be the web folder
$root = $_SERVER['DOCUMENT_ROOT'];
passthru("php $root/../app/console [...]");
The documentation has been updated since my last answer to reflect the proper Symfony 2 way of calling an existing command:
http://symfony.com/doc/current/components/console/introduction.html#calling-an-existing-command
I'm curios if Zend has a component which can make use of the shell. For example I want to do an shell command like this:
mysqldump --compact --uroot --ppass mydatabase mydable >test.sql
from a controller.
If there isn't, do you know a way how to dump data from tables in Zend Framework?
update:
I've found a way here http://www.zfsnippets.com/snippets/view/id/68
There's no direct exec() support in the zend framework. the closest to command line support there is the Zend_Console class, but it's meant for getting arguments from the command line.
I would wrap the exec() function as a process object and work with that. Here's a nice example from the php docs:
<?php
// You may use status(), start(), and stop(). notice that start() method gets called automatically one time.
$process = new Process('ls -al');
// or if you got the pid, however here only the status() metod will work.
$process = new Process();
$process.setPid(my_pid);
?>
<?php
// Then you can start/stop/ check status of the job.
$process.stop();
$process.start();
if ($process.status()){
echo "The process is currently running";
}else{
echo "The process is not running.";
}
?>
<?php
/* An easy way to keep in track of external processes.
* Ever wanted to execute a process in php, but you still wanted to have somewhat controll of the process ? Well.. This is a way of doing it.
* #compability: Linux only. (Windows does not work).
* #author: Peec
*/
class Process{
private $pid;
private $command;
public function __construct($cl=false){
if ($cl != false){
$this->command = $cl;
$this->runCom();
}
}
private function runCom(){
$command = 'nohup '.$this->command.' > /dev/null 2>&1 & echo $!';
exec($command ,$op);
$this->pid = (int)$op[0];
}
public function setPid($pid){
$this->pid = $pid;
}
public function getPid(){
return $this->pid;
}
public function status(){
$command = 'ps -p '.$this->pid;
exec($command,$op);
if (!isset($op[1]))return false;
else return true;
}
public function start(){
if ($this->command != '')$this->runCom();
else return true;
}
public function stop(){
$command = 'kill '.$this->pid;
exec($command);
if ($this->status() == false)return true;
else return false;
}
}
?>
It's also let you stop and check the status of a job.