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

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

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

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

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

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

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

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

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

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

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

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

StringInput

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

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

ArrayInput

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

接下来的改进

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

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

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

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

改进之后的代码:

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

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

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

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

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

原文地址:https://www.cnblogs.com/Jerry-blog/p/4919099.html