Click the Castor logo or press Ctrl Alt T to change theme.
# Process API The `Castor\Os\Process` class represents one child process opened with PHP's `proc_open()`. ```php namespace Castor\Os; use Castor\Io\Closer; use Castor\Os\Process\PipeReader; use Castor\Os\Process\PipeWriter; use Castor\Os\Process\Result; use Castor\Os\Process\TimeoutException; final class Process implements Closer ``` ## Process::run ```php /** * @param string|array<string> $command * @param null|array<string,string> $env */ public static function run(array|string $command, ?string $cwd = null, ?array $env = null, ?float $timeout = null): Result ``` Runs a process to completion and returns a `Castor\Os\Process\Result`. This method is designed for bounded commands where it is safe and useful to capture stdout and stderr as strings. ```php $result = Process::run(['php', '-r', 'fwrite(STDOUT, "ok");']); $result->exitCode; // 0 $result->stdout; // ok $result->stderr; // "" ``` `run()` starts the process, closes stdin immediately, waits for completion, reads stdout and stderr, closes the process, and returns the collected metadata. Use `start()` instead when you need to write interactive input, stream output while the process is running, or control termination manually. Pass `$timeout` as a number of seconds to enforce a total runtime limit. If the command exceeds that limit, `run()` throws `TimeoutException` and leaves the process streams available through the exception. ## Process::passthrough ```php /** * @param string|array<string> $command * @param array<string,string>|null $env */ public static function passthrough( array|string $command, Castor\Io\Writer $stdout, Castor\Io\Writer $stderr, ?string $cwd = null, ?array $env = null, ?float $timeout = null, ): Result ``` Runs a process to completion while forwarding stdout and stderr to the writers you provide. The output is also captured in the returned `Result`, so this method is useful when you want users to see live output without giving up the final strings. ```php use Castor\Io\Native\Stderr; use Castor\Io\Native\Stdout; use Castor\Os\Process; $result = Process::passthrough( ['composer', 'install'], stdout: Stdout::get(), stderr: Stderr::get(), ); $result->stdout; // captured stdout $result->stderr; // captured stderr ``` Unlike `start()`, `passthrough()` owns the process lifecycle. It closes stdin, drains stdout and stderr while the command is running, writes each chunk to the matching writer, closes the process, and returns a completed `Result`. Both writers are required. If you want to ignore one stream, pass a writer that discards bytes. If you want callback-style behavior, adapt the callback behind `Castor\Io\Writer` and pass that writer to this method. ## Process::start ```php /** * @param string|array<string> $command * @param null|array<string,string> $env */ public static function start(array|string $command, ?string $cwd = null, ?array $env = null, ?float $timeout = null): Process ``` Starts a process and returns a `Process` instance. - `$command` may be an argument array or a command string. - `$cwd` sets the child process working directory. `null` uses the parent process working directory. - `$env` sets environment variables as string key/value pairs. `null` uses PHP's default environment handling for `proc_open()`. - `$timeout` sets a default total runtime limit, in seconds, for later `wait()` calls. `null` means no timeout. Prefer argument arrays for normal commands: ```php $process = Process::start(['git', 'status', '--short'], cwd: '/srv/app'); ``` Use command strings only when you intentionally need shell behavior: ```php $process = Process::start('printf "%s\n" "$HOME"'); ``` ## stdin ```php public readonly PipeWriter $stdin ``` A Castor IO writer connected to the child process standard input. Common operations: ```php $process->stdin->write('request body'); $process->stdin->sendLine('interactive answer'); $process->stdin->close(); // signal EOF to the child ``` Close `stdin` after sending all data when the child reads until EOF. ## stdout ```php public readonly PipeReader $stdout ``` A Castor IO reader connected to the child process standard output. Common operations: ```php $output = Castor\Io\read_all($process->stdout); ``` Use direct reads, `read_all()`, `copy()`, or `Castor\Io\Buffered\Reader` depending on the amount and shape of output. ## stderr ```php public readonly PipeReader $stderr ``` A Castor IO reader connected to the child process standard error. Keep stderr separate from stdout so callers can preserve command semantics: ```php $stderr = Castor\Io\read_all($process->stderr); ``` ## isRunning ```php public function isRunning(): bool ``` Updates process status and returns whether the child process is currently running. ```php while ($process->isRunning()) { usleep(10_000); } ``` ## isSignaled ```php public function isSignaled(): bool ``` Updates process status and returns whether PHP reports that the process terminated because of a signal. Use with `getTermSignal()`: ```php if ($process->isSignaled()) { $signal = $process->getTermSignal(); } ``` ## isStopped ```php public function isStopped(): bool ``` Updates process status and returns whether PHP reports that the process is stopped. ## pid ```php public function pid(): int ``` Updates process status and returns the child process PID. Returns `-1` only when no PID is known. ## getExitCode ```php /** @return int<-1,255> */ public function getExitCode(): int ``` Updates process status and returns the normal process exit code. - Returns `-1` while the process is still running. - Returns `-1` when the process was terminated by signal. - Returns `0` through `255` after a normal exit. ```php $code = $process->getExitCode(); ``` ## wait ```php public function wait(?float $timeout = null): int ``` Blocks until `isRunning()` becomes false, then returns `getExitCode()`. ```php $code = $process->wait(); ``` `wait()` does not close the streams. Call `close()` after reading the output you need. If a timeout is passed to `wait()`, it overrides the default timeout from `start()`. Passing `null` uses the default from `start()`; it does not disable an already configured default timeout. When the effective timeout expires, `wait()` sends `SIGTERM` to the direct child process, waits briefly, sends `SIGKILL` if needed, and throws `TimeoutException`. The timeout must be a finite number greater than zero. ## TimeoutException ```php namespace Castor\Os\Process; final class TimeoutException extends RuntimeException ``` `TimeoutException` is thrown when `run()` or `wait()` exceeds its total runtime timeout. ```php try { Process::run(['sleep', '10'], timeout: 1.0); } catch (TimeoutException $error) { $process = $error->process; $timeout = $error->timeout; } ``` The exception exposes the still-open `Process` instance and the timeout value that expired. Read any remaining stdout or stderr you need, then call `close()`. ## terminate ```php public function terminate(int $signal = SIGTERM): void ``` Sends a termination signal to the process through `proc_terminate()`. ```php $process->terminate(); // SIGTERM $process->terminate(SIGKILL); // custom signal ``` The pipes remain open. Read any final output before closing the process if you need it. ## getTermSignal ```php public function getTermSignal(): int ``` Updates process status and returns the signal that terminated the process. Returns `-1` while the process is still running or when the process did not terminate because of a signal. ## close ```php public function close(): void ``` Closes `stdin`, `stdout`, and `stderr`, then closes the process resource with `proc_close()`. ```php $process->close(); ``` `close()` is idempotent. It is safe to call more than once. After the first close, later calls do nothing. The destructor calls `close()` as a last resort, but explicit `close()` is preferred so pipe and process lifetimes are deterministic. ## Result ```php namespace Castor\Os\Process; final readonly class Result ``` `Result` is the immutable value returned by `Process::run()`. It contains the command as it was provided, the normal exit code, captured output, process PID, elapsed duration, working directory, and termination signal metadata. ```php $result->command; $result->exitCode; $result->stdout; $result->stderr; $result->pid; $result->duration; $result->cwd; $result->termSignal; ``` Use `successful()` and `failed()` for simple exit-code checks: ```php if ($result->failed()) { throw new RuntimeException($result->stderr); } ``` `successful()` returns true only when the exit code is `0`. A signal-terminated process is represented with exit code `-1` and a non-negative `termSignal` when PHP reports one. ## PipeWriter::sendLine ```php public function sendLine(string $input): int ``` Writes `$input` followed by a newline to process stdin and returns the number of bytes written. It is a small convenience for interactive programs that read line-oriented input with functions such as `fgets()`. ```php $process = Process::start(['php', '-r', 'echo trim(fgets(STDIN));']); $process->stdin->sendLine('yes'); $process->stdin->close(); ``` ## Typical lifecycle ```php <?php use Castor\Os\Process; use function Castor\Io\read_all; $process = Process::start(['php', '-r', 'echo "ok";']); try { $code = $process->wait(); $stdout = read_all($process->stdout); $stderr = read_all($process->stderr); if ($code !== 0) { throw new RuntimeException($stderr); } } finally { $process->close(); } ```
Castor ecosystem