diff --git a/src/Child.php b/src/Child.php index 9f248be..2d0d18d 100644 --- a/src/Child.php +++ b/src/Child.php @@ -53,7 +53,7 @@ final class Child /** * @param resource $process The child's process resource handle. - * @param array $pipes + * @param array $pipes * The indexed array of file pointers that set by {@see \proc_open()}. * * @throws InvalidArgumentException diff --git a/src/ChildStderr.php b/src/ChildStderr.php index 95e4814..e2e0287 100644 --- a/src/ChildStderr.php +++ b/src/ChildStderr.php @@ -6,12 +6,18 @@ namespace Nih\CommandBuilder; use Override; +/** + * + */ final class ChildStderr implements StdioInterface { use StreamReadTrait; + /** + * @return resource + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return $this->stream; } diff --git a/src/ChildStdin.php b/src/ChildStdin.php index 870db17..63776f1 100644 --- a/src/ChildStdin.php +++ b/src/ChildStdin.php @@ -10,8 +10,11 @@ final class ChildStdin implements StdioInterface { use StreamWriteTrait; + /** + * @return resource + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return $this->stream; } diff --git a/src/ChildStdout.php b/src/ChildStdout.php index 054675b..cd24a4d 100644 --- a/src/ChildStdout.php +++ b/src/ChildStdout.php @@ -10,8 +10,11 @@ final class ChildStdout implements StdioInterface { use StreamReadTrait; + /** + * @return resource + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return $this->stream; } diff --git a/src/Command.php b/src/Command.php index ff564c9..130f7ff 100644 --- a/src/Command.php +++ b/src/Command.php @@ -17,9 +17,18 @@ final class Command implements Stringable { public readonly string $program; + /** + * @var list + */ private array $args = []; + + /** + * @var array + */ private ?array $environment = null; + private bool $environmentInherit = true; + private ?string $cwd = null; private ?StdioInterface $stdin = null; @@ -90,7 +99,7 @@ final class Command implements Stringable * escaped characters, word splitting, glob patterns, variable substitution, * etc. have no effect. * - * @param iterable $args + * @param iterable $args */ public function args(iterable $args): static { @@ -118,7 +127,7 @@ final class Command implements Stringable */ public function env(string $key, string|Stringable $val): static { - $this->environment[$key] = $val; + $this->environment[$key] = (string) $val; return $this; } @@ -285,13 +294,13 @@ final class Command implements Stringable { return $this->spawnWithDescriptorSpec([ $this->stdin instanceof StdioInterface - ? $this->stdin->getDescriptionSpec(0) + ? $this->stdin->getDescriptiorSpec(0) : STDIN, $this->stdout instanceof StdioInterface - ? $this->stdout->getDescriptionSpec(1) + ? $this->stdout->getDescriptiorSpec(1) : STDOUT, $this->stderr instanceof StdioInterface - ? $this->stderr->getDescriptionSpec(2) + ? $this->stderr->getDescriptiorSpec(2) : STDERR, ]); } @@ -322,13 +331,13 @@ final class Command implements Stringable { return $this->spawnWithDescriptorSpec([ $this->stdin instanceof StdioInterface - ? $this->stdin->getDescriptionSpec(0) + ? $this->stdin->getDescriptiorSpec(0) : ['pipe', 'r'], $this->stdout instanceof StdioInterface - ? $this->stdout->getDescriptionSpec(1) + ? $this->stdout->getDescriptiorSpec(1) : ['pipe', 'w'], $this->stderr instanceof StdioInterface - ? $this->stderr->getDescriptionSpec(2) + ? $this->stderr->getDescriptiorSpec(2) : ['pipe', 'w'], ])->waitWithOutput(); } @@ -345,6 +354,8 @@ final class Command implements Stringable * Returns the arguments that will be passed to the program. * * This does not include the path to the program as the first argument. + * + * @return list */ public function getArgs(): array { @@ -360,6 +371,8 @@ final class Command implements Stringable * * Note that this output does not include environment variables inherited * from the parent process. + * + * @return array */ public function getEnvs(): array { @@ -385,6 +398,9 @@ final class Command implements Stringable ]); } + /** + * @param array> $descriptorSpec + */ private function spawnWithDescriptorSpec(array $descriptorSpec): Child { // Find executable if path is not absolute. @@ -393,7 +409,7 @@ final class Command implements Stringable $path = getenv('PATH'); if (is_string($path)) { foreach (explode(':', $path) as $path) { - $path = $path . '/' . $program; + $path = $path . DIRECTORY_SEPARATOR . $program; if (is_executable($path)) { $program = $path; break; @@ -412,10 +428,15 @@ final class Command implements Stringable } } + $command = [$program]; + foreach ($this->args as $arg) { + $command[] = $arg; + } + try { set_error_handler(ChildException::handleError(...)); $proc = proc_open( - [$program, ...$this->args], + $command, $descriptorSpec, $pipes, $this->cwd, diff --git a/src/ExitStatus.php b/src/ExitStatus.php index 9cef6c4..dcf1133 100644 --- a/src/ExitStatus.php +++ b/src/ExitStatus.php @@ -11,6 +11,8 @@ namespace Nih\CommandBuilder; * child process. Child processes are created via the {@see Command} class and * their exit status is exposed through the status method, or the wait method of * a Child process. + * + * @api */ final readonly class ExitStatus { @@ -24,6 +26,8 @@ final readonly class ExitStatus /** * Was termination successful? Signal termination is not considered a * success, and success is defined as a zero exit status. + * + * @api */ public function success(): bool { @@ -34,6 +38,8 @@ final readonly class ExitStatus /** * The exit code of the process, if any. + * + * @api */ public function code(): ?int { @@ -42,6 +48,8 @@ final readonly class ExitStatus /** * If the process was terminated by a signal, returns that signal. + * + * @api */ public function signal(): ?int { @@ -50,6 +58,8 @@ final readonly class ExitStatus /** * If the process was stopped by a signal, returns that signal. + * + * @api */ public function stoppedSignal(): ?int { diff --git a/src/Stdio.php b/src/Stdio.php index b4de55b..d1b4439 100644 --- a/src/Stdio.php +++ b/src/Stdio.php @@ -23,13 +23,13 @@ abstract class Stdio { return new class implements StdioInterface { #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return match ($fd) { 0 => STDIN, 1 => STDOUT, 2 => STDERR, - default => ['php://fd/' . $fd, 'w+'], + default => ['file', 'php://fd/' . $fd, 'w+'], }; } }; @@ -41,8 +41,11 @@ abstract class Stdio public static function piped(): StdioInterface { return new class implements StdioInterface { + /** + * @return list + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return match ($fd) { 0 => ['pipe', 'r'], @@ -60,8 +63,11 @@ abstract class Stdio public static function null(): StdioInterface { return new class implements StdioInterface { + /** + * @return list + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { // TODO: Support windows (I think you just write to `nul` in any // directory?) @@ -86,8 +92,11 @@ abstract class Stdio private string $mode, ) {} + /** + * @return list + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return ['file', $this->filename, $this->mode]; } @@ -109,10 +118,17 @@ abstract class Stdio } return new class($stream) implements StdioInterface { - public function __construct(private mixed $stream) {} + /** + * @param resource $stream + */ + public function __construct(private mixed $stream) + {} + /** + * @return resource + */ #[Override] - public function getDescriptionSpec(int $fd): mixed + public function getDescriptiorSpec(int $fd): mixed { return $this->stream; } diff --git a/src/StdioInterface.php b/src/StdioInterface.php index 2b8fa98..3f050a7 100644 --- a/src/StdioInterface.php +++ b/src/StdioInterface.php @@ -4,9 +4,6 @@ declare(strict_types=1); namespace Nih\CommandBuilder; -use InvalidArgumentException; -use Stringable; - /** * Describes what to do with a standard I/O stream for a child process when * passed to the stdin, stdout, and stderr methods of {@see Command}. @@ -14,7 +11,7 @@ use Stringable; interface StdioInterface { /** - * @return resource|array{0: 'file', 1: string, 2: string}|array{0: 'pipe', 1: string} + * @return resource|list */ - public function getDescriptionSpec(int $fd): mixed; + public function getDescriptiorSpec(int $fd): mixed; } diff --git a/src/StreamReadTrait.php b/src/StreamReadTrait.php index 3c63f5e..c4ffad7 100644 --- a/src/StreamReadTrait.php +++ b/src/StreamReadTrait.php @@ -9,6 +9,8 @@ trait StreamReadTrait use StreamTrait; /** + * @param int<1, max> $length + * * @throws StreamException */ public function read(int $length): ?string @@ -30,6 +32,8 @@ trait StreamReadTrait } /** + * @param int<1, max>|null $length + * * @throws StreamException */ public function getContents(?int $length = null, int $offset = -1): string diff --git a/src/StreamTrait.php b/src/StreamTrait.php index eb3b822..55f8792 100644 --- a/src/StreamTrait.php +++ b/src/StreamTrait.php @@ -13,7 +13,7 @@ trait StreamTrait * * @throws InvalidArgumentException When stream is not a stream */ - public function __construct(private readonly mixed $stream) + public function __construct(private mixed $stream) { if (get_resource_type($stream) !== 'stream') { throw new InvalidArgumentException('resource is not a stream'); @@ -33,6 +33,13 @@ trait StreamTrait return; } + /** + * Psalm is not happy that we close the resource, but I could not + * find any way to annotate it so psalm understands what we do + * here... + * + * @psalm-suppress InvalidPropertyAssignmentValue + */ try { set_error_handler(StreamException::handleError(...)); $success = fclose($this->stream); diff --git a/src/StreamWriteTrait.php b/src/StreamWriteTrait.php index fdbdfd1..6329f54 100644 --- a/src/StreamWriteTrait.php +++ b/src/StreamWriteTrait.php @@ -9,6 +9,8 @@ trait StreamWriteTrait use StreamTrait; /** + * @param int<0, max>|null $length + * * @throws StreamException */ public function write(string $data, ?int $length = null): int