PHPから同期的に外部プログラムを呼び出すときにはexec関数ないしshell_exec関数を使います。非同期的に外部プログラムを呼び出すにはproc_open関数を使います。これらの関数はコンソール上でコマンドを叩くのとおおよそ同様に動作します。
PHP: exec – Manual
PHP: shell_exec – Manual
PHP: proc_open – Manual
いずれの場合も実行コマンド文字列を渡す必要のある関数です。PHPにはこういった実行コマンド用のエスケープ関数であるescapeshellarg, escapeshellcmdが用意されていますが、もし漏れがあり、それがコマンドインジェクションに繋がると悲惨なことに任意コード実行、事実上のサーバ乗っ取りまで起きます。そうなるとPHP実行ユーザの権限で許される限り好き放題されます。
PHP: escapeshellarg – Manual
PHP: escapeshellcmd – Manual
こういった問題を解決するためにはコマンドになる素材を渡すと自動でエスケープした状態でコマンドを実行する関数を用意するのが定番です。symfony/processはそういったもののリッチなバージョンです。エスケープによる安全さのみならず、同期実行、非同期実行の様なユースケースに合った動作の切り替え、標準出力や標準エラー出力をはじめとしたコマンドに関わる情報を得る便利機能までついています。
symfony/process – Packagist
symfony/processはSymfonyフレームワークの一パッケージです。Symfonyの派生であるLaravelにはデフォルトで入っています。単品で導入するには例によってcomposerでインストールです。使い方は例えば次です。
$option = '-latr'; $process = new Process(['ls', $option]); $process->run(); $output = $process->getOutput(); /** $outputの中身 * total 80 * drwxr-xr-x 2 root root 4096 Apr 11 2018 srv * drwxr-xr-x 2 root root 4096 Apr 11 2018 opt * drwxr-xr-x 2 root root 4096 Apr 11 2018 mnt * drwxr-xr-x 2 root root 4096 Apr 11 2018 media * lrwxrwxrwx 1 root root 8 Oct 1 10:15 sbin -> usr/sbin * lrwxrwxrwx 1 root root 9 Oct 1 10:15 lib64 -> usr/lib64 * lrwxrwxrwx 1 root root 7 Oct 1 10:15 lib -> usr/lib * lrwxrwxrwx 1 root root 7 Oct 1 10:15 bin -> usr/bin * drwxr-xr-x 1 root root 4096 Oct 1 10:15 usr * -rw-r--r-- 1 root root 12123 Oct 1 10:16 anaconda-post.log * drwxr-xr-x 3 root root 4096 Dec 29 23:27 boot * drwxr-xr-x 1 root root 4096 Dec 29 23:27 var * dr-xr-x--- 1 root root 4096 Jan 7 09:05 root * drwxr-xr-x 1 root root 4096 Jan 7 14:13 home * drwxr-xr-x 1 root root 4096 Jan 27 09:58 etc * -rwxr-xr-x 1 root root 0 Jan 27 09:58 .dockerenv * drwxr-xr-x 1 root root 4096 Jan 27 09:58 .. * drwxr-xr-x 1 root root 4096 Jan 27 09:58 . * dr-xr-xr-x 168 root root 0 Jan 27 17:52 proc * dr-xr-xr-x 13 root root 0 Jan 27 17:52 sys * drwxr-xr-x 5 root root 340 Jan 27 17:52 dev * drwxr-xr-x 1 root root 4096 Jan 27 17:52 run * drwxrwxrwt 1 root root 4096 Jan 27 17:52 tmp */
配列でコマンドになる文字列を渡してProcessインスタンスを生成。Processインスタンスをrunメソッドで同期実行するか、startメソッドで非同期実行するかします。すると外部プロセスが終了し次第、Processインスタンス中に標準出力などが代入され、getOutputメソッドなどで呼び出せるようになります。単にexecするよりもずっと安全で制御が効きます。
コマンドの元になる配列を組み上げるのはわりと面倒なのでBuilderパターンを用いるのがよいです。図は増補改訂版Java言語で学ぶデザインパターン入門 p.82から引用しました。
増補改訂版Java言語で学ぶデザインパターン入門 | 結城 浩 |本 | 通販 | Amazon
マンガでわかる Builder – Qiita
Builderパターン – Qiita
Builderパターンは簡単に言えば、パラメータなど何かしらを構築するプログラムのよくあるパターンです。PHPに縁のある人はおそらくSQLのクエリビルダが身近です。コマンドの場合、オプション関連があってもなくてもよくて -[a-zA-Z] 対象 の様な気持ち手間な文字列を大量に使うのでBuilderパターンを使う使わないで実装、呼び出しの手間がわりと変わります。例えば次の様な感じです。
class ListSegmentsCommand { protected $command = 'ls'; protected $options = []; public function setAllOption(){ $this->options[] = '-a'; return $this; } public function setLongOption(){ $this->options[] = '-l'; return $this; } // 上記の様なオプションをセットするメソッドを羅列 public function run(){ $process = new Process(Arr::flatten([$this->command, $this->options])) $process->run(); return $process; } } // 呼び出すならば $lsProcess = (new ListSegmentsCommand()) ->setAllOption() ->setLongOption() ->run(); $lsProcess->getOutput();