[Symfony2] 在命令或控制器里跑另一个命令的N种方法

或许最容易想到的,是通过system或者exec里执行命令,只不过这么做显得太过粗线条对吧——系统调用函数系列不一定主机提供商允许运行,而且运行命令得重新初始化Symfony2框架运行环境,多浪费计算资源。

这两个问题,最需要解决的是第一个问题。为了安全性,很多环境PHP的系统调用系列函数都被disable掉了。不过这个问题也应该好解决,我们来看看app/console文件到底执行了什么就明白了。

1

2

3

4

5

6

7

8

9

10

11

12

// app/console

...

use SymfonyBundleFrameworkBundleConsoleApplication;

use SymfonyComponentConsoleInputArgvInput;

...

$input = new ArgvInput();

...

$kernel = new AppKernel($env, $debug);

$application = new Application($kernel);

$application->run($input);

原来就是新建了一个Application对象并注入了$kernel就行了啊……且慢,输入的参数是怎么传入命令的呢?我们再看看Symfony\Component\Console\Input\ArgvInput类,看能不能发现什么:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// vendor/symfony/symfony/src/Symfony/Component/Console/Input/ArgInput.php

...

class ArgvInput extends Input

{

...

public function __construct(array $argv = null, InputDefinition $definition = null)

{

if (null === $argv) {

$argv = $_SERVER[‘argv‘];

}

// strip the application name

array_shift($argv);

$this->tokens = $argv;

parent::__construct($definition);

}

...

}

原来如此,ArgvInput在构建时,如果没有输入第一个参数,那么会自动采用$_SERVER[‘argv‘](其实也就是$argv变量)作为传入的参数。

当我们在命令行调用一个PHP脚本的时候,$argv是如下的样子:

1

2

3

4

5

6

7

8

$ php app/console foo:bar --foo

$argv = array(

0 => ‘app/console‘,

1 => ‘foo:bar‘,

3 => ‘--foo‘,

)

所以才会有array_shift($argv)一句,把app/console$argv里除掉,后面的处理并不需要它。

那么,我们是不是可以通过创建一个Application的方式来运行Symfony2项目里的命令了呢?当然可以!你只用构件好一个ArgvInput作为Application的第一个参数就可以了,比如调用cache:clear命令:

1

2

3

4

5

6

7

// namespace参见app/console文件

$input = new ArgvInput([‘app/console‘, ‘cache:clear‘]);

$kernel = new AppKernel($env, $debug);

$application = new Application($kernel);

$application->run($input); // 如果你在命令行里调用命令,你还可以把output作为第二参数传入

不过,在已有的命令或者控制器里,不必创建$kernel,因为$kernel已经有了,通过依赖注入容器你就可以获取:

1

2

$kernel = $this->getContainer()->get(‘kernel‘);

所以,之前$kernel = new AppKernel($env, $debug)那一行可以直接用上面的替换了,并且因为Kernel里已经包含了运行环境和debug开关等信息,你不用担心运行环境不一致的问题。

以上的代码可以运行,但需要传一个毫无意义的app/console,确实有点不舒服。我们再继续深入代码,看看能不能不用ArgvInput作为输入参数,毕竟Symfony Console Component里又不止它一个Input。

我们来看看Symfony/Component/Console/Input目录下实现InputInterface接口的类有啥。除了抽象类Input以外,还有它们:

StringInput

看名字就能大概猜出来使用方法。此类的构造函数的第一个参数为字符串,接下来我想不用我多说了吧。需要注意的是,命令里并不需要提到app/console

1

2

3

4

5

// 可将$input的创建替换为以下代码:

$input = new StringInput(‘cache:clear‘);

...

看起来很符合我们的需求哦。

ArrayInput

好吧,这个是数组版本的StringInput。没啥好说的,注意数组里除了第一个元素以外,其他的全是key => value形式:

1

2

3

4

5

// 可将$input的创建替换为以下代码:

$input = new ArrayInput([‘cache:clear‘]);

// 如果后面要接参数,必须是k/v形式:[‘doctrine:schema:update‘, ‘--force‘ => true];

接下来的改进

话说,为什么运行命令必须得初始化一个Application啊?

让我们来看看Application到底做了些啥:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

// vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php

...

class Application extends BaseApplication

{

...

public function __construct(KernelInterface $kernel)

{

$this->kernel = $kernel;

parent::__construct(‘Symfony‘, Kernel::VERSION.‘ - ‘.$kernel->getName().‘/‘.$kernel->getEnvironment().($kernel->isDebug() ? ‘/debug‘ : ‘‘));

$this->getDefinition()->addOption(new InputOption(‘--shell‘, ‘-s‘, InputOption::VALUE_NONE, ‘Launch the shell.‘));

$this->getDefinition()->addOption(new InputOption(‘--process-isolation‘, null, InputOption::VALUE_NONE, ‘Launch commands from shell as a separate process.‘));

$this->getDefinition()->addOption(new InputOption(‘--env‘, ‘-e‘, InputOption::VALUE_REQUIRED, ‘The Environment name.‘, $kernel->getEnvironment()));

$this->getDefinition()->addOption(new InputOption(‘--no-debug‘, null, InputOption::VALUE_NONE, ‘Switches off debug mode.‘));

}

...

public function doRun(InputInterface $input, OutputInterface $output)

{

$this->kernel->boot();

...

foreach ($this->all() as $command) {

if ($command instanceof ContainerAwareInterface) {

$command->setContainer($container);

}

}

$this->setDispatcher($container->get(‘event_dispatcher‘));

...

return parent::doRun($input, $output);

}

...

}

由此可见,从Application创建到执行run方法,做了下面这些事情:构造时注入了kernel并从kernel里获取了环境信息作为参数,并增了4个Symfony2命令必有的命令选项;运行时尝试启动kernel并做了一些初始化依赖的工作。且慢,代码里执行的是run命令,让我们来看看父类里run方法以及doRun方法做了什么:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

// vendor/symfony/symfony/src/Symfony/Component/Console/Application.php

...

class Application

{

public function run(InputInterface $input = null, OutputInterface $output = null)

{

...

try {

$exitCode = $this->doRun($input, $output);

} catch (Exception $e) {

...

}

return $exitCode;

}

public function doRun(InputInterface $input, OutputInterface $output)

{

...

$exitCode = $this->doRunCommand($command, $input, $output);

...

}

protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)

{

...

$event = new ConsoleCommandEvent($command, $input, $output);

$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);

if ($event->commandShouldRun()) {

try {

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

} catch (Exception $e) {

...

}

} else {

$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;

}

$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);

$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

return $event->getExitCode();

}

...

}

嗯,结果是一大段错误处理,以及抛出两个事件。真正执行的,还是$command->run($input, $output)这一句。如果大家还是想自己控制意外处理,并且不需要执行事件,其实是完全可以直接创建某个命令的实例并运行他的run方法。

改进之后的代码:

以执行doctrine:database:drop --force为例:

1

2

3

4

5

6

7

$command = new DropDatabaseDoctrineCommand();

$command->setContainer($container); // 如果是ContainerAwareCommand一定得用这句

$subInput = new InputStringInput(‘--force‘); // 注意因为我们是直接通过命令对象执行命令,所以参数中连命令名字都不需要了

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

目前为止,应该是性能最优的用代码调用Symfony2项目命令的方式了。

提示:是否使用最后一种方式来调用命令,也得分情况:如果你调用命令是为了批处理(按顺序执行n个命令),使用Application来运行命令更 适合,毕竟可能会有处理命令运行和终止事件的监听器。如果你只需要某个命令的功能,比如清空数据库,那么你最好使用最后一种方式来调用命令帮你完成任务。 不过需要注意的是,有的命令是依赖Application的,这种情况则需要使用$command->setApplication($application)或者使用Application来运行命令,再或者,将命令注册为服务:

1

2

3

4

5

app_bundle.command.my_command:

class: AppBundleCommandMyCommand

tags:

- { name: console.command }

当然,最好最好的方式:去看看你想调用的命令都执行了什么代码,把它们都找出来!

最后再给个友情提示:如果不想在console下输出信息,可以改用NullOutput

时间: 2024-10-10 03:47:40

[Symfony2] 在命令或控制器里跑另一个命令的N种方法的相关文章

Oracle删除一条SQL在Shared Pool里缓存的执行计划的三种方法

在Oracle里第一次执行一条SQL语句后,该SQL语句会被硬解析,而且执行计划和解析树会被缓存到Shared Pool里.方便以后再次执行这条SQL语句时不需要再做硬解析,方便应用系统的扩展.但是如果该SQL对应的表数据量突变或其他原因,Shared Pool里缓存的执行计划和解析树已经不再适用于现在的情况,SQL执行效率急速下降,这种情况下就需要把该SQL缓存在Shared Pool里的执行计划和解析树清理出去,以便对该SQL重新做硬解析,生成新的执行计划和解析树. 从Shared Pool

Apacheserver自己定义404页面的两种方法以及.htaccess的重要命令总结

Apacheserver自己定义404错误页面有两种方法: 第一种方法最简单,直接在Apache的httpd.conf下进行配置改动命令,改动的内容请參看.htaccess命令写法中的自己定义错误页面 另外一种方法能够在.htaccess文件里配置命令,详细操作请參看.htaccess命令写法中的自己定义错误页面 .htaccess用法总结 1 . 首先让的本地APACHEserver器同意.htaccess改动 打开httpd.conf (1) Options FollowSymLinks A

DOS常用命令,及DOS下可运行程序命令

一.内部基本指令(文件操作) 1 dir 无参数:查看当前所在目录的文件和文件夹. /s:查看当前目录已经其所有子目录的文件和文件夹. /a:查看包括隐含文件的所有文件. /ah:只显示出隐含文件. /w:以紧凑方式(一行显示5个文件)显示文件和文件夹. /p:以分页方式(显示一页之后会自动暂停)显示. |more:前面那个符号是"\"上面的那个,叫做重定向符号,就是把一个 命令的结果输出为另外一个命令的参数.more也是一个命令,dir /w |more 得到的结果和dir /w /

LINUX基础命令的使用以及vim的简单命令

一 命令提示符说明 [登录用户@主机名 工作目录]# 二 Linux命令格式 命令字  [选项]  [参数1] [参数2] [ ] 表示里面内容可有可无 选项:如果是单个字符,用-               如:# ls -l 如果是一个单词,用--              如:# ls --color 多个单个字符的选项可以合并一个-   如:# ls -l -h = # ls –lh 三 基本命令及其说明 1. 基本命令 # uname -r//查询内核 # cat /etc/redha

asp.net.mvc 中form表单提交控制器的2种方法和控制器接收页面提交数据的4种方法

MVC中表单form是怎样提交? 控制器Controller是怎样接收的? 1..cshtml 页面form提交 (1)普通方式的的提交 (2)特殊方式提交 2.控制器处理表单数据的四种方法 方法1:使用传统的Request请求数据 方法2:Action参数名与表单元素name值一一对应 方法3:从MVC封装的FormCollection容器中读取 方法4:使用实体作为Action参数传入,前提是提交的表单元素名称与实体属性名称一一对应 控制器源码 using MvcStudy.Models;u

Symfony2 学习笔记之控制器

一个controller是你创建的一个PHP函数,它接收HTTP请求(request)并创建和返回一个HTTP回复(Response).回复对象(Response)可以是一个HTML页面,一个XML文档,一个序列化的JSON数组,一个图片,一个重定向,一个404错误或者任何你想要的内容.controller中可以包含任何渲染你页面内容的所需要的逻辑. 下面是一个controller最简单的例子,仅仅打印一个Hello world! use Symfony\Component\HttpFounda

[转]Windows中的命令行提示符里的Start命令执行路径包含空格时的问题

转自:http://www.x2009.net/articles/windows-command-line-prompt-start-path-space.html 当使用Windows 中的命令行提示符执行这段指令时(测试Start命令执行带空格的路径的程序或文件问题),第一行Start会成功执行,跳出记事本程序,而第二行,会 Start跳出一个新的命令提示符,标题上写着路径,但是不会执行任何命令,第三行Start命令行提示符会提示C:\Program文件不存在,提示无 法执行. start

两条命令让cocos2d-x项目跑在android上

cocos2d-x3.0以来,目录中就多了一个叫setup.py的配置文件.运行它可以帮助我们迅速的配置必要的环境变量:cocos2d-x命令行的路径,android_sdk的路径,ant_root的路径,android_ndk的路径. 首先自然是要下载cocos2d-x3.0之后的版本,下载android_sdk,android_ndk,和ant. 等环境变量配置完成,我们就可以开始我们的新项目了. 我们使用cocos new命令来生成一个新的cocos2d-x项目. 所以只需输入 cocos

MVC操作LocalDB数据库,通过电影类型和名称来查询电影,在控制器里写的方法以及页面代码,自己参考。

//按电影类别来查询电影并排列 public ActionResult Index(string movieGenre, string searchString) { //可以放string类型的空的泛型集合 var Genrelst = new List<string>(); //下面的代码是从数据库中检索所有类型的LINQ 查询. var GenreQry = from d in db.Movies orderby d.Genre select d.Genre; //把不重复的电影类别放在