]> BookStack Code Mirror - system-cli/blob - scripts/Services/ProgramRunner.php
acc51b959b66206102d62e0e021a50640dd438af
[system-cli] / scripts / Services / ProgramRunner.php
1 <?php
2
3 namespace Cli\Services;
4
5 use Symfony\Component\Process\ExecutableFinder;
6 use Symfony\Component\Process\Process;
7
8 class ProgramRunner
9 {
10     protected int $timeout = 240;
11     protected int $idleTimeout = 15;
12     protected array $environment = [];
13     protected array $additionalProgramDirectories = [];
14
15     public function __construct(
16         protected string $program,
17         protected string $defaultPath
18     ) {
19     }
20
21     public function withTimeout(int $timeoutSeconds): static
22     {
23         $this->timeout = $timeoutSeconds;
24         return $this;
25     }
26
27     public function withIdleTimeout(int $idleTimeoutSeconds): static
28     {
29         $this->idleTimeout = $idleTimeoutSeconds;
30         return $this;
31     }
32
33     public function withEnvironment(array $environment): static
34     {
35         $this->environment = $environment;
36         return $this;
37     }
38
39     public function withAdditionalPathLocation(string $directoryPath): static
40     {
41         $this->additionalProgramDirectories[] = $directoryPath;
42         return $this;
43     }
44
45     public function runCapturingAllOutput(array $args): string
46     {
47         $output = '';
48         $callable = function ($data) use (&$output) {
49             $output .= $data . "\n";
50         };
51
52         $this->runWithoutOutputCallbacks($args, $callable, $callable);
53         return $output;
54     }
55
56     public function runCapturingStdErr(array $args): string
57     {
58         $err = '';
59         $this->runWithoutOutputCallbacks($args, fn() => '', function ($data) use (&$err) {
60             $err .= $data . "\n";
61         });
62         return $err;
63     }
64
65     public function runWithoutOutputCallbacks(array $args, callable $stdOutCallback = null, callable $stdErrCallback = null): int
66     {
67         $process = $this->startProcess($args);
68         foreach ($process as $type => $data) {
69             if ($type === $process::ERR) {
70                 if ($stdErrCallback) {
71                     $stdErrCallback($data);
72                 }
73             } else {
74                 if ($stdOutCallback) {
75                     $stdOutCallback($data);
76                 }
77             }
78         }
79
80         return $process->getExitCode() ?? 1;
81     }
82
83     /**
84      * @throws \Exception
85      */
86     public function ensureFound(): void
87     {
88         $this->resolveProgramPath();
89     }
90
91     protected function startProcess(array $args): Process
92     {
93         $programPath = $this->resolveProgramPath();
94         $process = new Process([$programPath, ...$args], null, $this->environment);
95         $process->setTimeout($this->timeout);
96         $process->setIdleTimeout($this->idleTimeout);
97         $process->start();
98         return $process;
99     }
100
101     protected function resolveProgramPath(): string
102     {
103         $executableFinder = new ExecutableFinder();
104         $path = $executableFinder->find($this->program, $this->defaultPath, $this->additionalProgramDirectories);
105
106         if (is_null($path) || !is_file($path)) {
107             throw new \Exception("Could not locate \"{$this->program}\" program.");
108         }
109
110         return $path;
111     }
112 }