标签:
或许最容易想到的,是通过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
以外,还有它们:
看名字就能大概猜出来使用方法。此类的构造函数的第一个参数为字符串,接下来我想不用我多说了吧。需要注意的是,命令里并不需要提到app/console
:
1
2
3
4
5
|
// 可将$input的创建替换为以下代码:
$input = new StringInput(‘cache:clear‘);
...
|
看起来很符合我们的需求哦。
好吧,这个是数组版本的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
哦
[Symfony2] 在命令或控制器里跑另一个命令的N种方法
标签:
原文地址:http://www.cnblogs.com/Jerry-blog/p/4919099.html