programNames = is_string($program) ? [$program] : $program; } public function withTimeout(int $timeoutSeconds): static { $this->timeout = $timeoutSeconds; return $this; } public function withIdleTimeout(int $idleTimeoutSeconds): static { $this->idleTimeout = $idleTimeoutSeconds; return $this; } public function withEnvironment(array $environment): static { $this->environment = $environment; return $this; } public function withAdditionalPathLocation(string $directoryPath): static { $this->additionalProgramDirectories[] = $directoryPath; return $this; } public function runCapturingAllOutput(array $args): string { $output = ''; $callable = function ($data) use (&$output) { $output .= $data; }; $this->runWithoutOutputCallbacks($args, $callable, $callable); return $output; } public function runCapturingStdErr(array $args): string { $err = ''; $this->runWithoutOutputCallbacks($args, fn() => '', function ($data) use (&$err) { $err .= $data; }); return $err; } public function runWithoutOutputCallbacks(array $args, callable $stdOutCallback = null, callable $stdErrCallback = null): int { $process = $this->startProcess($args); foreach ($process as $type => $data) { if ($type === $process::ERR) { if ($stdErrCallback) { $stdErrCallback($data); } } else { if ($stdOutCallback) { $stdOutCallback($data); } } } return $process->getExitCode() ?? 1; } /** * @throws Exception */ public function ensureFound(): void { $this->resolveProgramPath(); } public function isFound(): bool { try { $this->ensureFound(); return true; } catch (Exception $exception) { return false; } } protected function startProcess(array $args): Process { $programPath = $this->resolveProgramPath(); $process = new Process([$programPath, ...$args], null, $this->environment); $process->setTimeout($this->timeout); $process->setIdleTimeout($this->idleTimeout); $process->start(); return $process; } /** * @throws Exception */ protected function resolveProgramPath(): string { foreach ($this->programNames as $programName) { $executableFinder = new ExecutableFinder(); $path = $executableFinder->find($programName, $this->defaultPath, $this->additionalProgramDirectories); if (!is_null($path) && is_file($path)) { return $path; } } $combinedProgram = implode('|', $this->programNames); throw new Exception("Could not locate \"{$combinedProgram}\" program."); } }