Process组件

3.4 版本
维护中的版本

Process组件在子进程(sub-processes)中执行命令。

超高难内容 译注:本文档并非孤立,建议与Console组件一起学习。本文需要较为高深的computer学科背景知识,请学者注意。

安装 

你可以通过下述两种方式安装:

然后,包容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();
}

获取实时Process输出 

当执行一个长时间运行的命令时(比如同步文件到远程服务器),你可以把反馈信息实时地返给末级用户,这时要传递一个匿名函数给 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;
    }
});

---流化Process的标准Input---

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代码 

如果你希望在孤立状态下执行一些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();

Process超时 

你可以限制进程在完成过程中所耗费的时间数,设置一个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#5759PHP Bug#39992 以了解为何会如此。

POSIX signals在Windows平台不可用,参考 PHP文档 了解可用的信号。

进程的Pid 

你可以访问运行中的进程的 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 来理解为何会发生这样的事。

关闭Output 

由于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,将不能使用 getOutputgetIncrementalOutputgetErrorOutputgetIncrementalErrorOutput 或是 setIdleTimeout

然而,传入一个callback给 startrunmustRun ,以streaming方式来处理进程的output,是可以的。

3.1 在output被关闭时,“向这些方法传入callback”的能力,从Symofny3.1开始被添加进来。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)