1 <?php declare(strict_types=1);
3 namespace Cli\Services;
6 use Symfony\Component\Process\ExecutableFinder;
7 use Symfony\Component\Process\Process;
12 * Names of the program to search for.
15 protected array $programNames = [];
17 protected int $timeout = 240;
18 protected int $idleTimeout = 15;
19 protected array $environment = [];
20 protected array $additionalProgramDirectories = [];
22 public function __construct(
23 string|array $program,
24 protected string $defaultPath
26 $this->programNames = is_string($program) ? [$program] : $program;
29 public function withTimeout(int $timeoutSeconds): static
31 $this->timeout = $timeoutSeconds;
35 public function withIdleTimeout(int $idleTimeoutSeconds): static
37 $this->idleTimeout = $idleTimeoutSeconds;
41 public function withEnvironment(array $environment): static
43 $this->environment = $environment;
47 public function withAdditionalPathLocation(string $directoryPath): static
49 $this->additionalProgramDirectories[] = $directoryPath;
53 public function runCapturingAllOutput(array $args): string
56 $callable = function ($data) use (&$output) {
60 $this->runWithoutOutputCallbacks($args, $callable, $callable);
64 public function runCapturingStdErr(array $args): string
67 $this->runWithoutOutputCallbacks($args, fn() => '', function ($data) use (&$err) {
73 public function runWithoutOutputCallbacks(array $args, callable $stdOutCallback = null, callable $stdErrCallback = null): int
75 $process = $this->startProcess($args);
76 foreach ($process as $type => $data) {
77 if ($type === $process::ERR) {
78 if ($stdErrCallback) {
79 $stdErrCallback($data);
82 if ($stdOutCallback) {
83 $stdOutCallback($data);
88 return $process->getExitCode() ?? 1;
94 public function ensureFound(): void
96 $this->resolveProgramPath();
99 public function isFound(): bool
102 $this->ensureFound();
104 } catch (Exception $exception) {
109 protected function startProcess(array $args): Process
111 $programPath = $this->resolveProgramPath();
112 $process = new Process([$programPath, ...$args], null, $this->environment);
113 $process->setTimeout($this->timeout);
114 $process->setIdleTimeout($this->idleTimeout);
122 protected function resolveProgramPath(): string
124 foreach ($this->programNames as $programName) {
125 $executableFinder = new ExecutableFinder();
126 $path = $executableFinder->find($programName, $this->defaultPath, $this->additionalProgramDirectories);
128 if (!is_null($path) && is_file($path)) {
133 $combinedProgram = implode('|', $this->programNames);
134 throw new Exception("Could not locate \"{$combinedProgram}\" program.");