Click the Castor logo or press Ctrl Alt T to change theme.
# Process IO Castor Process is designed to compose with [`castor/io`](https://castor-labs.github.io/php-lib-io/). The process pipes are exposed as small IO interfaces instead of raw PHP resources. ## Stream types A `Process` has three public readonly stream properties: | Property | Interfaces | Direction | Meaning | | --- | --- | --- | --- | | `stdin` | `Castor\Os\Process\PipeWriter` | parent writes to child | Bytes sent to the child process standard input. | | `stdout` | `Castor\Os\Process\PipeReader` | parent reads from child | Bytes emitted by the child process standard output. | | `stderr` | `Castor\Os\Process\PipeReader` | parent reads from child | Bytes emitted by the child process standard error. | `PipeWriter` implements `Castor\Io\Writer` and `Castor\Io\Closer`. `PipeReader` implements `Castor\Io\Reader` and `Castor\Io\Closer`. They are backed by Castor IO's native stream adapter, so you can treat them according to those interfaces: write to `stdin`, read from `stdout` and `stderr`, and close them when done. ## Read all output For small bounded output, `collect()` or `Castor\Io\read_all()` are the simplest options: ```php <?php use Castor\Os\Process; use function Castor\Io\read_all; $process = Process::start(['php', '-r', 'echo "out"; fwrite(STDERR, "err");']); $code = $process->wait(); $stdout = $process->stdout->collect(); $stderr = $process->stderr->collect(); $process->close(); ``` Both `collect()` and `read_all()` read until `Castor\Io\EndOfFile` and return the accumulated string. They are convenient, but they store the whole stream in memory. ## Pass output through to writers Use `Process::passthrough()` when you want a command to behave like it is attached to the current terminal or another pair of Castor IO writers, while still receiving a completed `Result` at the end. ```php <?php use Castor\Io\Native\Stderr; use Castor\Io\Native\Stdout; use Castor\Os\Process; $result = Process::passthrough( ['php', '-r', 'echo "out\\n"; fwrite(STDERR, "err\\n");'], stdout: Stdout::get(), stderr: Stderr::get(), ); $result->stdout; // "out\n" $result->stderr; // "err\n" ``` The method requires both writers because passthrough is its purpose: stdout and stderr are drained while the process is running and each chunk is written to the corresponding writer. The same bytes are captured into the returned result, so callers can display progress live and still inspect the output afterwards. Any `Castor\Io\Writer` can be used. For example, a small callback-backed writer can turn process output into log events: ```php <?php use Castor\Io\Writer; final readonly class CallbackWriter implements Writer { /** * @param callable(string): void $callback */ public function __construct( private mixed $callback, ) {} public function write(string $bytes): int { ($this->callback)($bytes); return strlen($bytes); } } ``` Then pass separate writers for each stream: ```php Process::passthrough( ['composer', 'install'], stdout: new CallbackWriter(fn (string $chunk) => $logger->info($chunk)), stderr: new CallbackWriter(fn (string $chunk) => $logger->error($chunk)), ); ``` Use `start()` instead when you need to write to stdin interactively, coordinate the process with an event loop, or manually decide when and how to read from the process pipes. ## Copy output to another writer Use `Castor\Io\copy()` when you want to stream process output into another Castor IO writer after manually starting a process. ```php <?php use Castor\Io\Native\Output; use Castor\Os\Process; use function Castor\Io\copy; $process = Process::start(['php', '-r', 'echo "streamed output\\n";']); $output = Output::open(); $bytes = copy($process->stdout, $output); $code = $process->wait(); $process->close(); ``` `copy()` avoids building one large string in your own code. It still blocks until EOF, so use it in an order that fits the child process behavior. ## Direct reads The underlying interface is `Reader::read(int $length, ?string &$buff): int`. It returns the number of bytes read and writes the bytes into the by-reference buffer. ```php $buff = null; $bytes = $process->stdout->read(8192, $buff); ``` A read may return fewer bytes than requested. Keep reading until you have what you need or until the reader throws `Castor\Io\EndOfFile`. ## Buffered and line-oriented reads Wrap a process reader in `Castor\Io\Buffered\Reader` when you want userland buffering, line reads, peeking, delimiter reads, or byte reads: ```php <?php use Castor\Io\Buffered\Reader; use Castor\Os\Process; $process = Process::start(['php', '-r', 'echo "one\\ntwo\\n";']); $stdout = new Reader($process->stdout); while (true) { try { $stdout->readLine($line); echo $line; } catch (Castor\Io\EndOfFile) { break; } } $code = $process->wait(); $process->close(); ``` This pattern is useful for long-running commands that emit progress lines. ## Writing stdin Write bytes to `stdin` with the Castor IO writer interface: ```php $process = Process::start(['php', '-r', 'echo stream_get_contents(STDIN);']); $process->stdin->write('payload'); $process->stdin->close(); $code = $process->wait(); ``` Close `stdin` after sending all input unless the child command expects an interactive session. Many programs read until EOF; if EOF never arrives, they keep waiting. For commands that read one line at a time, `PipeWriter::sendLine()` keeps the call site readable: ```php $process = Process::start(['php', '-r', 'echo trim(fgets(STDIN));']); $process->stdin->sendLine('continue'); $process->stdin->close(); $code = $process->wait(); $output = $process->stdout->collect(); // continue $process->close(); ``` `sendLine()` writes the given string plus a trailing newline and returns the number of bytes written. ## Large output and deadlocks Operating-system pipes have finite buffers. If the child writes enough data to fill `stdout` or `stderr` while the parent is only calling `wait()`, the child can block trying to write. Then the parent keeps waiting for a process that cannot finish. For small commands, this is usually fine: ```php $code = $process->wait(); $output = read_all($process->stdout); ``` For commands that may produce large output, drain the relevant streams while the process is running. Depending on your program, that can mean reading stdout first, reading stderr first, copying streams to files, using non-blocking/event-loop coordination around the underlying process, or designing the child command to produce bounded output. Also remember that `stdout` and `stderr` are separate pipes. If the child can write a lot to both, drain both streams; reading only one can still leave the other full. ## Closing streams `Process::close()` closes `stdin`, `stdout`, and `stderr` for you. You may close an individual pipe earlier when that is part of the protocol, most commonly closing `stdin` to signal EOF. After a stream is closed, further reads or writes through that stream can throw `Castor\Io\Error`.
Castor ecosystem