@title Command Execution
@group exec
This document describes best practices for executing system commands in PHP
using libphutil.
= Overview =
PHP has several built-in mechanisms for executing system commands, like
`exec()`, `system()`, and the backtick operator. However, these mechanisms
often make it difficult to get all the information you need to handle error
conditions, properly escaping commands is cumbersome, and they do not provide
more advanced features like parallel execution and timeouts.
This document describes how to use the APIs in libphutil to execute commands
without encountering these problems.
= Simple Commands: `execx()` and `exec_manual()` =
@{function:execx} and @{function:exec_manual} are replacements for `exec()`,
`system()`, `shell_exec()`, and the backtick operator. The APIs look like this:
list($stdout, $stderr) = execx('ls %s', $path);
list($err, $stdout, $stderr) = exec_manual('ls %s', $path);
The major advantages of these methods over the `exec()` family are that you can
easily check return codes, capture both stdout and stderr, and use a simple
`sprintf()`-style formatting string to properly escape commands.
@{function:execx} will throw a @{class:CommandException} if the command you
execute terminates with a nonzero exit code, while @{function:exec_manual}
returns the error code. If you use @{function:exec_manual}, you must manually
check the error code.
= Advanced Commands: `ExecFutures` =
If you need more advanced features like parallel execution, command timeouts,
and asynchronous I/O, use @{class:ExecFuture}.
$future = new ExecFuture('ls %s', $path);
list($stdout, $stderr) = $future->resolvex();
@{class:ExecFuture} is a @{class:Future}, and can be used with constructs like
@{class:FutureIterator} to achieve and manage parallelism. See
@{article:Using Futures} for general information on how to use futures in
libphutil.
In addition to futures-based parallelism, you can set a timeout on an
@{class:ExecFuture}, which will kill the command if it takes longer than the
specified number of seconds to execute:
$future->setTimeout(30);
If the command runs longer than the timeout, the process will be killed and the
future will resolve with a failure code (`ExecFuture::TIMED_OUT_EXIT_CODE`).
You can also write to the stdin of a process by using the
@{method:ExecFuture::write} method.
$future = new ExecFuture('bc');
$future->write('2+2');
list($stdout) = $future->resolvex();
See @{class:ExecFuture} for complete capability documentation.