command = $command; $this->args[] = $this->command; } public function arg(string|Stringable $arg): static { $this->args[] = (string) $arg; return $this; } /** * @param array $args */ public function args(array $args): static { foreach ($args as $arg) { $this->args[] = (string) $arg; } return $this; } public function currentDir(string|Stringable $cwd): static { $this->cwd = (string) $cwd; return $this; } public function env(string $var, string|Stringable $value): static { $this->envs[$var] = escapeshellarg((string) $value); return $this; } /** * @param array $envs */ public function envs(array $envs): static { foreach ($envs as $var => $value) { $this->envs[$var] = escapeshellarg((string) $value); } return $this; } public function stdin(Stdio $stdin): static { $stdin = match ($stdin->type) { Stdio::INHERIT => Stdio::stream(STDIN), Stdio::PIPE => new Stdio(Stdio::PIPE, ['pipe', 'r']), default => $stdin, }; $this->stdin = $stdin; return $this; } public function stdout(Stdio $stdout): static { $stdout = match ($stdout->type) { Stdio::INHERIT => Stdio::stream(STDOUT), Stdio::PIPE => new Stdio(Stdio::PIPE, ['pipe', 'w']), default => $stdout, }; $this->stdout = $stdout; return $this; } public function stderr(Stdio $stderr): static { $stderr = match ($stderr->type) { Stdio::INHERIT => Stdio::stream(STDERR), Stdio::PIPE => new Stdio(Stdio::PIPE, ['pipe', 'w']), default => $stderr, }; $this->stderr = $stderr; return $this; } // TODO: Allow capturing arbitrary descriptors (proc_open supports this)? // public function descriptor(int $fd, Stdio $stdio): static // { // match ($fd) { // 0 => $this->stdin($stdio), // 1 => $this->stdout($stdio), // 2 => $this->stderr($stdio), // default => $this->fd[$fd] = $stdio, // }; // // return $this; // } /** * @param bool $shell Run the command with or without a shell */ public function spawn(bool $shell = true): Child { return $this->spawnWithDescriptorSpec($shell, [ $this->stdin instanceof Stdio ? $this->stdin->descriptorSpec : STDIN, $this->stdout instanceof Stdio ? $this->stdout->descriptorSpec : STDOUT, $this->stderr instanceof Stdio ? $this->stderr->descriptorSpec : STDERR, ]); } /** * @param bool $shell Run the command with or without a shell */ public function status(bool $shell = true): ExitStatus { return $this->spawn($shell)->wait(); } /** * @param bool $shell Run the command with or without a shell */ public function output(bool $shell = true): Output { return $this->spawnWithDescriptorSpec($shell, [ $this->stdin instanceof Stdio ? $this->stdin->descriptorSpec : ['pipe', 'r'], $this->stdout instanceof Stdio ? $this->stdout->descriptorSpec : ['pipe', 'w'], $this->stderr instanceof Stdio ? $this->stderr->descriptorSpec : ['pipe', 'w'], ])->waitWithOutput(); } public function __toString(): string { return implode(' ', array_map(escapeshellarg(...), $this->args)); } private function spawnWithDescriptorSpec(bool $shell, array $descriptorSpec): Child { foreach ($descriptorSpec as $descriptor => $spec) { if (!is_array($spec) && !is_resource($spec)) { throw new CommandException(sprintf( 'Descriptor %d is not a valid stream resource: %s', $descriptor, get_debug_type($spec), )); } } if ($shell) { $command = (string) $this; } else if (is_executable($this->command)) { $command = $this->args; } else { throw new CommandException(sprintf( 'Command "%s" is not executable', $this->command, )); } $proc = proc_open($command, $descriptorSpec, $pipes, $this->cwd, $this->envs); if ($proc === false) { throw new RuntimeException('Failed proc_open'); } $stdin = array_key_exists(0, $pipes) ? new ChildStdin($pipes[0]) : null; $stdout = array_key_exists(1, $pipes) ? new ChildStdout($pipes[1]) : null; $stderr = array_key_exists(2, $pipes) ? new ChildStderr($pipes[2]) : null; return new Child($stdin, $stdout, $stderr, $proc); } }