diff --git a/src/Command.php b/src/Command.php index cbfa54c..616c4a7 100644 --- a/src/Command.php +++ b/src/Command.php @@ -10,15 +10,14 @@ use ValueError; /** * A process builder, providing fine-grained control over how a new process * should be spawned. - * - * TODO: Wonky env handling. */ final class Command implements Stringable { public readonly string $program; private array $args; - private ?array $envs = null; + private ?array $environment = null; + private bool $environmentInherit = true; private ?string $cwd = null; private ?Stdio $stdin = null; @@ -26,7 +25,17 @@ final class Command implements Stringable private ?Stdio $stderr = null; /** + * Constructs a new Command for launching the program at path program, with + * the following default configuration: * + * - No arguments to the program + * - Inherit the current process's environment + * - Inherit the current process's working directory + * - Inherit stdin/stdout/stderr for spawn or status, but create pipes for + * output + * + * Builder methods are provided to change these defaults and otherwise + * configure the process. */ public function __construct(string $program) { @@ -40,6 +49,22 @@ final class Command implements Stringable /** * Adds an argument to pass to the program. + * + * Only one argument can be passed per use. So instead of: + * + * ``` + * $command->arg('-C /path/to/repo'); + * ``` + * + * usage would be: + * + * ``` + * $command + * ->arg('-C') + * ->arg('/path/to/repo'); + * ``` + * + * To pass multiple arguments see {@see Command::args()}. */ public function arg(string|Stringable $arg): static { @@ -50,6 +75,8 @@ final class Command implements Stringable /** * Adds multiple arguments to pass to the program. * + * To pass a single argument see {@see Command::arg()}. + * * @param iterable $args */ public function args(iterable $args): static @@ -63,22 +90,45 @@ final class Command implements Stringable /** * Inserts or updates an explicit environment variable mapping. + * + * This method allows you to add an environment variable mapping to the + * spawned process or overwrite a previously set value. You can use + * {@see Command::envs()} to set multiple environment variables + * simultaneously. + * + * Child processes will inherit environment variables from their parent + * process by default. Environment variables explicitly set using + * {@see Command::env()} take precedence over inherited variables. You can + * disable environment variable inheritance entirely using + * {@see Command::envClear()} or for a single key using + * {@see Command::envRemove()}. */ public function env(string $key, string|Stringable $val): static { - $this->envs[$key] = $val; + $this->environment[$key] = $val; return $this; } /** * Inserts or updates multiple environment variable mappings. * + * This method allows you to add multiple environment variable mappings to + * the spawned process or overwrite previously set values. You can use + * {@see Command::env()} to set a single environment variable. + * + * Child processes will inherit environment variables from their parent + * process by default. Environment variables explicitly set using + * {@see Command::envs()} take precedence over inherited variables. You can + * disable environment variable inheritance entirely using + * {@see Command::envClear()} or for a single key using + * {@see Command::envRemove()}. + * * @param iterable $vars */ public function envs(iterable $vars): static { foreach ($vars as $key => $val) { - $this->envs[$key] = (string) $val; + $this->environment[$key] = (string) $val; } return $this; @@ -87,27 +137,42 @@ final class Command implements Stringable /** * Removes an explicitly set environment variable and prevents inheriting it * from a parent process. + * + * This method will remove the explicit value of an environment variable set + * via {@see Command::env()} or {@see Command::envs()}. In addition, it will + * prevent the spawned child process from inheriting that environment + * variable from its parent process. + * + * After calling {@see Command::envRemove()}, the value associated with its + * key from {@see Command::getEnvs()} will be `NULL`. + * + * To clear all explicitly set environment variables and disable all + * environment variable inheritance, you can use {@see Command::envClear()}. */ public function envRemove(string $key): static { - if (is_null($this->envs)) { - $this->envs = getenv(); - } - - if (isset($this->envs[$key])) { - unset($this->envs[$key]); - } - + $this->environment[$key] = null; return $this; } /** * Clears all explicitly set environment variables and prevents inheriting * any parent process environment variables. + * + * This method will remove all explicitly added environment variables set + * via {@see Command::env()} or {@see Command::envs()}. In addition, it will + * prevent the spawned child process from inheriting any environment + * variable from its parent process. + * + * After calling {@see Command::envClear()}, the array from + * {@see Command::getEnvs()} will be empty. + * + * You can use {@see Command::envRemove()} to clear a single mapping. */ public function envClear(): static { - $this->envs = []; + $this->environmentInherit = false; + $this->environment = []; return $this; } @@ -266,10 +331,17 @@ final class Command implements Stringable /** * Returns the environment variables set for the child process. + * + * Environment variables explicitly set using {@see Command::env(), + * {@see Command::envs()}, and {@see Command::envRemove} can be retrieved + * with this method. + * + * Note that this output does not include environment variables inherited + * from the parent process. */ public function getEnvs(): array { - return $this->envs; + return $this->environment ?? []; } /** @@ -310,7 +382,16 @@ final class Command implements Stringable )); } - $proc = proc_open($command, $descriptorSpec, $pipes, $this->cwd, $this->envs); + $environment = $this->environment; + if (is_array($environment) && $this->environmentInherit) { + foreach (getenv() as $key => $val) { + if (!array_key_exists($key, $environment)) { + $environment[$key] = $val; + } + } + } + + $proc = proc_open($command, $descriptorSpec, $pipes, $this->cwd, $environment); if ($proc === false) { throw new CommandException(sprintf( 'Program "%s" failed to start',