amphp/process
Asynchronous process dispatcher for PHP (AMPHP) built for fibers and concurrency. Start and manage child processes cross-platform, stream stdout/stderr without blocking, set working directory and environment variables, powered by the Revolt event loop and Windows wrapper.
Start by installing via Composer:
composer require amphp/process
Ensure your project uses an AMP-based async framework (e.g., amphp/amp, Danack/Anvil, or AsyncPHP/HttpServer) since this package relies on the AMP event loop. The most basic usage spawns a process, waits for output, and exits:
use Amp\Process\Process;
use Amp\Promise;
require 'vendor/autoload.php';
async function main(): Promise {
$process = new Process(['php', '-r', 'echo "Hello from async!\n";']);
$process->start();
$output = '';
foreach ($process->getStdout() as $chunk) {
$output .= $chunk;
}
$exitCode = await $process->join();
echo "Exit code: $exitCode\nOutput: $output\n";
}
Amp\Loop::run(fn() => main());
Look first at the Process class documentation and the examples/ directory in the repo for quick, runnable demos.
stdout()/stderr() to consume data incrementally (e.g., tail logs or stream build output):
foreach ($process->getStderr() as $chunk) {
error_log("STDERR: $chunk");
}
write() and handle responses:
$process = new Process(['cat']);
$process->start();
$process->write("hello\n");
$process->write("world\n");
$process->closeStdin(); // Signal EOF
Amp\Promise + Amp\Timeout to kill hung processes:
$timedOut = false;
$process = new Process(['sleep', '10']);
$process->start();
try {
await $process->join(1000); // 1s timeout
} catch (Amp\TimeoutException $e) {
$timedOut = true;
$process->terminate();
}
$promises = [];
foreach ($files as $file) {
$process = new Process(['convert', $file, 'thumb_' . $file]);
$process->start();
$promises[] = $process->join();
}
await Amp\Promise\all($promises);
SIGINT/SIGTERM in CLI tools to gracefully kill child processes:
$process->onExit(fn($signal) => Logger::info("Process terminated by signal $signal"));
stdout/stderr, the process can deadlock due to full buffers (especially with interactive tools like ssh or python -i). Always yield or foreach the streams—even if you ignore the content.chdir() or pass absolute paths in $process->start([...], ['cwd' => '/path/to/dir']).write() returns a Promise<void> — you must await it if you rely on buffering guarantees. Failure to do so may drop input silently during high throughput.join() blocks until exit, but you can pass a timeout in milliseconds (e.g., await $process->join(5000)) to prevent indefinite hangs.terminate(), kill()) relies on POSIX signals — limited support on Windows. Prefer terminate() over kill() for cross-platform safety.proc_open(): Running synchronous calls in the same loop breaks async semantics. Use amphp/process exclusively for all process management when in an AMP context.stream_select fallback — ensure your environment supports async I/O. Check with php -m | grep -E 'uv|event'.How can I help you explore Laravel packages today?