超PHPerになろう

Enjoy PHP Programming

外部プログラムを起動するには [symfony/process]

PHPスクリプトから他のプログラムを起動して、その出力結果を得たいことがあります。PHPではexecproc_openがあります。しかし、標準出力(stdout)と標準エラー出力(stderr)をうまく制御して出力を取得するのは、実はなかなか面倒だし、マニュアルを読みながら自分で一から実装するのは不毛です。

symfony/processを利用すれば、簡単で安全に外部プログラムを利用できるようになります。

パッケージ名 Packagist: symfony/process
作者 fabpot (Fabien Potencier)
ライセンス MIT License
バージョン v2.7.6 (2015-10-27)

インストール

Composerでインストールできます。

cd /your/project
composer.phar require symfony/process

特徴

  • 標準の機能よりも簡潔で利用しやすい
  • Unix/WindowsなどOSの差異を吸収してくれる
  • バイナリセーフ

つかひかた

動作確認のために、標準出力(stdout)と標準エラー出力(stderr)に書き込むテスト用のスクリプト/tmp/test_outputに用意します。

#!/usr/bin/env php
<?php

echo "AAAA\n";
fwrite(STDERR, "1111\n");
echo "BBBB\n";
fwrite(STDERR, "2222\n");

呼び出す側のコードは以下のようになります。

<?php
use Symfony\Component\Process\Process;

$process = new Process('php /tmp/test_output');
$process->run();

// 標準出力の内容を取り出す
$stdout = $process->getOutput();
//=> "AAAA\nBBBB\n"

// 標準エラー出力の内容を取り出す
$process->getErrorOutput();
//=> "1111\n2222\n"

タイムアウトを指定したい

プロセス起動前に$process->setTimeout()を読んでタイムアウト時間を指定すると、ProcessTimedOutExceptionが送出されるようになります。

<?php
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

// 1秒待ったあとで "123" を出力する
$process = new Process(' php -r" sleep(1); echo 123; " ');

// 処理に0.5秒以上かかったら停止する
$process->setTimeout(0.5);
try {
    $process->run();
} catch (ProcessTimedOutException $e) {
    echo "停止しました。";
}

var_dump($process->getOutput());
//=> NULL

プロセスが停止されてなければ123が出力されてるはずですが、タイムアウト指定がきっちり機能してるようです。

正常終了しなければ例外

外部プログラムが異常終了すると作業を続行できないような処理では$process->mustRun()で実行すると、終了ステータスが0以外のときProcessFailedExceptionを送出するようになります。

<?php
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

$process = new Process('php -r" exit(1); "');
try {
    $process->mustRun();
} catch (ProcessFailedException $e) {
    echo $e->getMessage();
    exit(1);
}

例外が発生するケースが極めて稀な場合は、try-catchで括らずプログラム全体を異常停止させた方が良いように感じます。

注意

処理の大小に拘らず、プロセス起動は時間がかかります。特にWebアプリケーションでは何度も起動すると容易にボトルネックとなりえますので、本当に必要なことだけを外部プログラムで処理させるようにするべきです。

データを加工するような処理であれば、PHPで実装されたライブラリが存在しないかPackagistで検索してみると良いかもしれません。