如何从控制器运行 symfony 2 运行命令

Posted

技术标签:

【中文标题】如何从控制器运行 symfony 2 运行命令【英文标题】:How can I run symfony 2 run command from controller 【发布时间】:2012-05-08 11:18:24 【问题描述】:

我想知道如何从浏览器查询或控制器运行 Symfony 2 命令。

这是因为我没有任何可能在托管上运行它,并且每个 cron 作业都是由管理员设置的。

我什至没有启用exec() 功能,所以当我想测试它时,我必须将命令中的所有内容复制到某个测试控制器,这不是最佳解决方案。

【问题讨论】:

【参考方案1】:

在这个问题上查看 official documentation 以获得较新版本的 Symfony


您不需要从控制器执行命令的服务,我认为最好通过run 方法而不是通过控制台字符串输入调用命令,但是official docs 建议您通过它的别名调用命令。另请参阅this answer。在 Symfony 2.1-2.6 上测试。

您的命令类必须扩展 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);
    

在大多数情况下,您不需要BufferedOutput(来自Jbm的答案),检查$resultCode is 0就足够了,否则会出错。

【讨论】:

我在 2.4 中没有 setContainer 方法 您还需要将执行的命令名称作为ArrayInput 的第一个元素(至少在Symfony2.6 上)。 如果命令很耗时,您可能需要增加执行时间以避免超时:ini_set('max_execution_time', 600); 也是如此。 这感觉完全过于复杂了 Symfony 强迫开发人员在这里做的事情......我的意思是认真的 :)【参考方案2】:

将您的命令注册为服务,不要忘记致电setContainer

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

在您的控制器中,您只需获取此服务,并使用权限参数调用执行方法

setArgument方法设置输入:

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

调用命令的run方法:

$command = $this->get('MyCommandService');
$command->run($input, $output);

【讨论】:

$output var 用于捕获控制台回复?谢谢 是的,有writeLine的方法可以在控制台写一行 谢谢,这很有帮助!尽管如此,我在服务中包装教义:generate:entity 命令时遇到了一些问题。因为: 1) 命令的 HelperSet 为空。 2) isInteractive 为真。解决方案:1)在我的服务构造函数中,我实例化了一个应用程序并将它的 helperSet 传递给命令:$application = new Symfony\Component\Console\Application();$this->doctrineGenEntityCmd->setHelperSet($application->getHelperSet());2)$input->setInteractive(false)【参考方案3】:

在我的环境(Symony 2.1)中,我必须对@Reuven 解决方案进行一些修改才能使其正常工作。他们在这里:

服务定义 - 没有变化。

在控制器中:

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);

【讨论】:

【参考方案4】:

您可以简单地创建一个命令实例并运行它:

/**
 * @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();

【讨论】:

【参考方案5】:

这是一种替代方法,可让您以与在控制台上相同的方式将命令作为字符串执行(无需使用此定义服务)。

您可以查看this bundle's controller 以了解所有详细信息是如何完成的。在这里,我将对其进行总结,省略某些细节(例如处理环境,因此这里所有命令都将在它们被调用的同一环境中运行)。

如果你只想从浏览器运行命令,你可以直接使用那个包,但是如果你想从任意控制器运行命令,这里是如何做到的:

在你的控制器中定义一个这样的函数:

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;

然后你可以从这样的动作中调用它:

public function dumpassetsAction()

    $output = $this->execute('assetic:dump');

    return new Response($output);

另外,你需要定义一个类作为输出缓冲区,因为框架没有提供:

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;
    

【讨论】:

在 LiipFunctionalTestBundle 中还有一个相当不错的实现,它使用 php://temp 流和内置的 StreamOutput 类来捕获输出,因此您不需要自己创建第二个输出缓冲区类. github.com/liip/LiipFunctionalTestBundle/blob/master/Test/… 请注意,BufferedOutput 从 Symfony 2.4 开始可用。【参考方案6】:

与@malloc 相同 但是

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);

【讨论】:

【参考方案7】:

如果您必须传递参数(和/或选项),那么在 v2.0.12 中(以后版本可能是这样),您需要在实例化输入对象之前先指定 InputDefinition。

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();

附带说明一下,如果您在命令中使用 getContainer(),则以下函数可能对您的 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();

【讨论】:

【参考方案8】:

您可以使用此捆绑包从控制器(http 请求)运行 Symfony2 命令并在 URL 中传递选项/参数。

https://github.com/mrafalko/CommandRunnerBundle

【讨论】:

【参考方案9】:

如果您运行需要env 选项的命令,例如assetic:dump

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

您必须创建一个Symfony\Component\Console\Application 并像这样设置定义:

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);

您无法将选项 env 设置为命令,因为它不在其定义中。

【讨论】:

Silex 应用扩展了 Pimple Container 并实现了 HttpKernelInterface、TerminableInterface,并且没有setDefinition 方法。

以上是关于如何从控制器运行 symfony 2 运行命令的主要内容,如果未能解决你的问题,请参考以下文章

如何在symfony 2.x中设置控制台命令的定时器?

运行命令doctrine:schema:update symfony时出错

如何从 Symfony 进程运行 vi?

从带有命令行参数的批处理文件运行控制台应用程序,如何在运行时传递参数值? [复制]

Symfony 进程组件 - 为命令设置输入参数

运行 composer install 命令时出错