STDIN, 1 => STDOUT, 2 => STDERR, default => ['php://fd/' . $fd, 'w+'], }; } }; } /** * A new pipe should be arranged to connect the parent and child processes. */ public static function piped(): StdioInterface { return new class implements StdioInterface { #[Override] public function getDescriptionSpec(int $fd): mixed { return match ($fd) { 0 => ['pipe', 'r'], 1, 2 => ['pipe', 'w'], default => ['pipe', 'w+'], }; } }; } /** * This stream will be ignored. This is the equivalent of attaching the * stream to `/dev/null`. */ public static function null(): StdioInterface { return new class implements StdioInterface { #[Override] public function getDescriptionSpec(int $fd): mixed { // TODO: Support windows (I think you just write to `nul` in any // directory?) return match ($fd) { 0 => ['file', '/dev/null', 'r'], 1, 2 => ['file', '/dev/null', 'w'], default => ['file', '/dev/null', 'w+'], }; } }; } /** * Like piped, but instead of capturing the stream into a handle, read * and/or write from/to a file. */ public static function file(string|Stringable $filename, string $mode): StdioInterface { return new class((string) $filename, $mode) implements StdioInterface { public function __construct( private string $filename, private string $mode, ) {} #[Override] public function getDescriptionSpec(int $fd): mixed { return ['file', $this->filename, $this->mode]; } }; } /** * Like piped, but instead of capturing the stream into a handle, read * and/or write from/to a stream resource. * * @param resource $stream * * @throws InvalidArgumentException When stream is not a stream resource */ public static function stream($stream): StdioInterface { if (get_resource_type($stream) !== 'stream') { throw new InvalidArgumentException('resource is not a stream'); } return new class($stream) implements StdioInterface { public function __construct(private mixed $stream) {} #[Override] public function getDescriptionSpec(int $fd): mixed { return $this->stream; } }; } }