How can I run symfony 2 run command from controller
Asked Answered
A

9

45

I'm wondering how can I run Symfony 2 command from browser query or from controller.

Its because I don't have any possibility on hosting to run it and every cron jobs are setted by admin.

I don't even have enabled exec() function so when I want to test it, I must copy all content from command to some testing controller and this is not best solution.

Ambidexter answered 8/5, 2012 at 11:18 Comment(0)
F
66

See official documentation on this issue for newer versions of Symfony


You don't need services for command execution from controller and, I think, it is better to call command via run method and not via console string input, however official docs suggest you to call command via it's alias. Also, see this answer. Tested on Symfony 2.1-2.6.

Your command class must extend ContainerAwareCommand

// Your command

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

class MyCommand extends ContainerAwareCommand {
    // …
}


// Your controller

use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;

class SomeController extends Controller {

    // …

    public function myAction()
    {
        $command = new MyCommand();
        $command->setContainer($this->container);
        $input = new ArrayInput(array('some-param' => 10, '--some-option' => true));
        $output = new NullOutput();
        $resultCode = $command->run($input, $output);
    }
}

In most cases you don't need BufferedOutput (from Jbm's answer) and it is enough to check that $resultCode is 0, otherwise there was an error.

Finable answered 7/8, 2013 at 11:29 Comment(4)
I don't get a setContainer method in 2.4Dysplasia
You also need to put the executed command name as first element of the ArrayInput (at least on Symfony2.6).Sawtoothed
If the command is time-consuming you'll probably need to increase the execution time in order to avoid a timeout: ini_set('max_execution_time', 600); as well.Goodman
This feels totally overcomplicated what Symfony forces the developer to do here... I mean seriously :)Jackleg
K
57

Register your command as a service and don't forget to call setContainer

MyCommandService:
    class: MyBundle\Command\MyCommand
    calls:
        - [setContainer, ["@service_container"] ]

In your controller, you'll just have to get this service, and call the execute method with the rights arguments

Set the input with setArgument method:

$input = new Symfony\Component\Console\Input\ArgvInput([]);
$input->setArgument('arg1', 'value');
$output = new Symfony\Component\Console\Output\ConsoleOutput();

Call the run method of the command:

$command = $this->get('MyCommandService');
$command->run($input, $output);
Kirima answered 8/5, 2012 at 12:58 Comment(3)
$output var is for capture the console reply? ThanksCloris
yes, there is a writeLine method to write a line in the consoleKirima
Thanks, this was very helpful! Still, I had some issues wrapping doctrine:generate:entity Command in a service. Because of: 1) The Command's HelperSet being null. 2) The isInteractive being true. Solutions: 1) in my service's constructor I instantiated an Application and passed it's helperSet to the Command: $application = new Symfony\Component\Console\Application(); $this->doctrineGenEntityCmd->setHelperSet($application->getHelperSet()); 2) $input->setInteractive(false)Imperator
E
10

In my environment ( Symony 2.1 ) I had to do some modifications to @Reuven solution to make it work. Here they are:

Service definition - no changes.

In controller:

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

...

public function myAction() {
    $command = $this->get('MyCommandService');

    $input = new ArgvInput(array('arg1'=> 'value'));
    $output = new ConsoleOutput();

    $command->run($input, $output);
}
Enlistee answered 13/12, 2012 at 9:38 Comment(0)
B
4

You can just simply create an instance of your command and run it:

/**
 * @Route("/run-command")
 */
public function someAction()
{
    // Running the command
    $command = new YourCommand();
    $command->setContainer($this->container);

    $input = new ArrayInput(['--your_argument' => true]);
    $output = new ConsoleOutput();

    $command->run($input, $output);

    return new Response();
}
Bestraddle answered 20/12, 2013 at 14:4 Comment(0)
T
3

Here's an alternative that lets you execute commands as strings the same way you would on the console (there is no need for defining services with this one).

You can check this bundle's controller to see how it's done with all the details. Here I'm going to summarize it ommiting certain details (such as handling the environment, so here all commands will run in the same environment they are invoked).

If you want to just run commands from the browser, you can use that bundle as it is, but if you want to run commands from an arbitrary controller here is how to do it:

In your controller define a function like this:

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\StringInput;

private function execute($command)
{
    $app = new Application($this->get('kernel'));
    $app->setAutoExit(false);

    $input = new StringInput($command);
    $output = new BufferedOutput();

    $error = $app->run($input, $output);

    if($error != 0)
        $msg = "Error: $error";
    else
        $msg = $output->getBuffer();
    return $msg;
}

Then you can invoke it from an action like this:

public function dumpassetsAction()
{
    $output = $this->execute('assetic:dump');

    return new Response($output);
}

Also, you need to define a class to act as output buffer, because there is none provided by the framework:

use Symfony\Component\Console\Output\Output;

class BufferedOutput extends Output
{
    public function doWrite($message, $newline)
    {
        $this->buffer .= $message. ($newline? PHP_EOL: '');
    }

    public function getBuffer()
    {
        return $this->buffer;
    }
}
Truthful answered 29/3, 2013 at 21:45 Comment(2)
There is also a quite nice implementation in the LiipFunctionalTestBundle that uses php://temp stream with the built in StreamOutput class to capture the output so you don't need to create a second output buffer class yourself. github.com/liip/LiipFunctionalTestBundle/blob/master/Test/…Lazuli
Just a note to say that BufferedOutput is now available since Symfony 2.4.Pierson
E
2

same as @malloc but

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

...

public function myAction() {
  $command = $this->get('MyCommandService');

  // $input[0] : command name
  // $input[1] : argument1
  $input = new ArgvInput(array('my:command', 'arg1'));
  $output = new ConsoleOutput();

  $command->run($input, $output);
}
Endicott answered 18/12, 2013 at 23:23 Comment(0)
H
1

If you have to pass arguments (and/or options), then in v2.0.12 (and may be true for later versions), you need to specify InputDefinition first before instantiating an input object.

use // you will need the following
    Symfony\Component\Console\Input\InputOption,
    Symfony\Component\Console\Input\InputArgument,
    Symfony\Component\Console\Input\InputDefinition,
    Symfony\Component\Console\Input\ArgvInput,
    Symfony\Component\Console\Output\NullOutput;


// tell symfony what to expect in the input
$inputDefinition = new InputDefinition(array(
    new InputArgument('myArg1', InputArgument::REQUIRED),
    new InputArgument('myArg2', InputArgument::REQUIRED),
    new InputOption('debug', '0', InputOption::VALUE_OPTIONAL),
));


// then pass the values for arguments to constructor, however make sure 
// first param is dummy value (there is an array_shift() in ArgvInput's constructor)
$input = new ArgvInput(
                        array(
                                'dummySoInputValidates' => 'dummy', 
                                'myArg2' => 'myValue1', 
                                'myArg2' => 'myValue2'), 
                        $inputDefinition);
$output = new NullOutput();



As a side note, if you are using if you are using getContainer() in your command, then the following function may be handy for your command.php:

/**
 * Inject a dependency injection container, this is used when using the 
 * command as a service
 * 
 */
function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null)
{
    $this->container = $container;
}

/**
 * Since we are using command as a service, getContainer() is not available
 * hence we need to pass the container (via services.yml) and use this function to switch
 * between conatiners..
 *
 */
public function getcontainer()
{
    if (is_object($this->container))
        return $this->container;

    return parent::getcontainer();
}
Hyperacidity answered 6/11, 2013 at 21:9 Comment(0)
B
0

You can use this bundle to run Symfony2 commands from controller (http request) and pass options/parameters in URL.

https://github.com/mrafalko/CommandRunnerBundle

Blessington answered 30/4, 2014 at 20:11 Comment(0)
S
0

If you run a command that need the env option like assetic:dump

$stdout->writeln(sprintf('Dumping all <comment>%s</comment> assets.', $input->getOption('env')));

You have to create a Symfony\Component\Console\Application and set the definition like that:

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOuput;

// Create and run the command of assetic
$app = new Application();
$app->setDefinition(new InputDefinition([
    new InputOption('env', '', InputOption::VALUE_OPTIONAL, '', 'prod')
]));
$app->add(new DumpCommand());

/** @var DumpCommand $command */
$command = $app->find('assetic:dump');
$command->setContainer($this->container);
$input = new ArgvInput([
    'command' => 'assetic:dump',
    'write_to' => $this->assetsDir
]);
$output = new NullOutput();
$command->run($input, $output);

You can't set the option env to the command because it isn't in its definition.

Somite answered 16/4, 2015 at 15:15 Comment(1)
Silex Application extends Pimple Container and implements HttpKernelInterface, TerminableInterface, and has no setDefinition method.Barrelchested

© 2022 - 2024 — McMap. All rights reserved.