支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
Process组件在子进程(sub-processes)中执行命令。
超高难内容 译注:本文档并非孤立,建议与Console组件一起学习。本文需要较为高深的computer学科背景知识,请学者注意。
你可以通过下述两种方式安装:
通过Composer安装(Packagist上的symfony/process
)
通过官方Git宝库(https://github.com/symfony/process)
然后,包容vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
Process
类允许你在子进程中执行命令:
1 2 3 4 5 6 7 8 9 10 11 12 | use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
$process = new Process('ls -lsa');
$process->run();
// executes after the command finishes / 命令完成后执行
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
echo $process->getOutput(); |
本组件负责处理在执行命令行时“不同平台之间的微妙区别”。
getOutput()
方法始终返回“命令的标准输出(standard output)”的全部内容,而 getErrorOutput()
返回的是error output的内容。可以选择 getIncrementalOutput()
和 getIncrementalErrorOutput()
方法来返回“自上次调用后”的全新内容。
clearOutput()
方法清除output内容, clearErrorOutput()
清除error output的内容。
3.1 对一个进程的流化输出(streaming the output)支持,是从Symofny3.1开始引入的。
你也可以使用 Process
类,利用foreach结构在生成output的过程中来获取它。默认时,循环要在下一次遍历之前等待新的output:
1 2 3 4 5 6 7 8 9 10 | $process = new Process('ls -lsa');
$process->start();
foreach ($process as $type => $data) {
if ($process::OUT === $type) {
echo "\nRead from stdout: ".$data;
} else { // $process::ERR === $type
echo "\nRead from stderr: ".$data;
}
} |
mustRun()
方法等同于 run()
,除了它将抛出一个 ProcessFailedException
异常,如果进程未按预期成功执行的话 (比如process 报 non-zero code 而退出):
1 2 3 4 5 6 7 8 9 10 11 12 | use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
try {
$process->mustRun();
echo $process->getOutput();
} catch (ProcessFailedException $e) {
echo $e->getMessage();
} |
当执行一个长时间运行的命令时(比如同步文件到远程服务器),你可以把反馈信息实时地返给末级用户,这时要传递一个匿名函数给 run()
方法:
1 2 3 4 5 6 7 8 9 10 | use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
}); |
你也可以启用子进程,并让它异步运行,在你需要的时候,取出output和主进程状态。使用 start()
方法来开启异步进程, isRunning()
方法用于检查进程是否完成, getOutput()
方法取得输出:
1 2 3 4 5 6 7 8 | $process = new Process('ls -lsa');
$process->start();
while ($process->isRunning()) {
// waiting for process to finish / 等待进程完成
}
echo $process->getOutput(); |
异步启动时,你也可以等待进程结束,然后再做些事情:
1 2 3 4 5 6 7 8 9 | $process = new Process('ls -lsa');
$process->start();
// ... do other things / 做一些事
$process->wait();
// ... do things after the process has finished
// .. 在进程完成后做一些事 |
wait()
方法是屏蔽的,意味着你的代码将在这一行停止,直到外部进程完成为止。
wait()
接收一个可选参数:一个被反复调用的callback,同时进程仍然保持运行,向匿名函数传入output及其type:
1 2 3 4 5 6 7 8 9 10 | $process = new Process('ls -lsa');
$process->start();
$process->wait(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
}); |
3.1 对一个进程的input进行流化,是从Symfony 3.1开始支持的。
在进程启动之前,你可以使用 setInput()
方法,或者构造器的第4个参数,来指定它的standard input(标准输入)。提供的input可以是字符串,stream资源,或者是一个Traversable对象:
1 2 3 | $process = new Process('cat');
$process->setInput('foobar');
$process->run(); |
当input被完全写到子进程的standard input时,对应的pipe将关闭。
为了能对一个“正在运行”的子进程编写standard input,本组件提供了inputStream
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $input = new InputStream();
$input->write('foo');
$process = new Process('cat');
$process->setInput($input);
$process->start();
// ... read process output or do other things
// ... 读取进程的output,或者做其他事
$input->write('bar');
$input->close();
$process->wait();
// will echo: foobar / 将执行echo: foobar
echo $process->getOutput(); |
write()
方法可接收scalar标量,stream resources流化资源,或是Traversable object可遍历对象作为参数。如同上例所示,你需要在写完子进程的standard input之后,显式地指定 close()
方法。
任何一个异步进程都可以通过 stop()
方法在任何时间中止。本方法接收两个参数:一个是timeout(超时),一个是signal(信号)。一旦达到时限,信号就会被发往正在运行的进程。默认的发往进程的signal是 SIGKILL
。请参考 下文的signal文档 来了解Process组件中关于“信号处理”(signal handling)的更多内容:
1 2 3 4 5 6 | $process = new Process('ls -lsa');
$process->start();
// ... do other things / 做其他事
$process->stop(3, SIGINT); |
如果你希望在孤立状态下执行一些PHP代码,使用 PhpProcess
来替代:
1 2 3 4 5 6 7 | use Symfony\Component\Process\PhpProcess;
$process = new PhpProcess(<<<EOF
<?php echo 'Hello World'; ?>
EOF
);
$process->run(); |
要确保代码能更好地运行于全部平台之上,你可能需要使用 ProcessBuilder
类来替代:
1 2 3 4 | use Symfony\Component\Process\ProcessBuilder;
$builder = new ProcessBuilder(array('ls', '-lsa'));
$builder->getProcess()->run(); |
如果你在构建一个二进制驱动(binary driver),可以使用 setPrefix()
方法来对所有“已生成”的进程命令添加前缀:
下例将为一个tar binary adapter生成两个进程命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use Symfony\Component\Process\ProcessBuilder;
$builder = new ProcessBuilder();
$builder->setPrefix('/usr/bin/tar');
// '/usr/bin/tar' '--list' '--file=archive.tar.gz'
echo $builder
->setArguments(array('--list', '--file=archive.tar.gz'))
->getProcess()
->getCommandLine();
// '/usr/bin/tar' '-xzf' 'archive.tar.gz'
echo $builder
->setArguments(array('-xzf', 'archive.tar.gz'))
->getProcess()
->getCommandLine(); |
你可以限制进程在完成过程中所耗费的时间数,设置一个timeout(以秒计)即可:
1 2 3 4 5 | use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->setTimeout(3600);
$process->run(); |
如果抵达时限,一个 RuntimeException
异常会抛出。
对于长耗时命令,你的职责就是对超时进行例行检查:
1 2 3 4 5 6 7 8 9 10 11 12 | $process->setTimeout(3600);
$process->start();
while ($condition) {
// ...
// check if the timeout is reached
// 检查是否到达时限
$process->checkTimeout();
usleep(200000);
} |
相对于前面的超时,空间超时(idle timeout)只计算从“上一次output被进程输出”开始的时间:
1 2 3 4 5 6 | use Symfony\Component\Process\Process;
$process = new Process('something-with-variable-runtime');
$process->setTimeout(3600);
$process->setIdleTimeout(60);
$process->run(); |
上例如,一个进程将在总体运行时间超过3600秒,或是60秒内进程没有产生任何output时,被认定超时。
当异步运行一个程序时,你可以发送通过 signal()
方法来发送POSIX signals:
1 2 3 4 5 6 7 8 | use Symfony\Component\Process\Process;
$process = new Process('find / -name "rabbit"');
$process->start();
// will send a SIGKILL to the process
// 将发送一个SIGKILL到进程
$process->signal(SIGKILL); |
Caution
由于PHP的某些限制,如果你在Process组件中使用signals,你可能不得不对命令施以 exec 前缀。参考 Symfony Issue#5759 和 PHP Bug#39992 以了解为何会如此。
POSIX signals在Windows平台不可用,参考 PHP文档 了解可用的信号。
你可以访问运行中的进程的 pid,通过 getPid()
方法。
1 2 3 4 5 6 | use Symfony\Component\Process\Process;
$process = new Process('/usr/bin/php worker.php');
$process->start();
$pid = $process->getPid(); |
由于PHP的某些限制,如果你要取得一个symfony进程的pid,你可能不得不对自己的命令添加 exec 前缀。参考 Symfony Issue#5759 来理解为何会发生这样的事。
由于standard output和error output总是从背后的进程(underlying process)中被取出,在某些需要节约内存的场景下,关闭output可能更方便。使用 disableOutput()
和 enableOutput()
来切换此功能:
1 2 3 4 5 | use Symfony\Component\Process\Process;
$process = new Process('/usr/bin/php worker.php');
$process->disableOutput();
$process->run(); |
进程在运行时,你可以开启或关闭输出。
如果你关闭output,将不能使用 getOutput
、getIncrementalOutput
、getErrorOutput
、getIncrementalErrorOutput
或是 setIdleTimeout
。
然而,传入一个callback给 start
、run
或 mustRun
,以streaming方式来处理进程的output,是可以的。
3.1 在output被关闭时,“向这些方法传入callback”的能力,从Symofny3.1开始被添加进来。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。