]> BookStack Code Mirror - system-cli/blob - scripts/Services/ProgramRunner.php
Added update command
[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     public function isFound(): bool
92     {
93         try {
94             $this->ensureFound();
95             return true;
96         } catch (\Exception $exception) {
97             return false;
98         }
99     }
100
101     protected function startProcess(array $args): Process
102     {
103         $programPath = $this->resolveProgramPath();
104         $process = new Process([$programPath, ...$args], null, $this->environment);
105         $process->setTimeout($this->timeout);
106         $process->setIdleTimeout($this->idleTimeout);
107         $process->start();
108         return $process;
109     }
110
111     protected function resolveProgramPath(): string
112     {
113         $executableFinder = new ExecutableFinder();
114         $path = $executableFinder->find($this->program, $this->defaultPath, $this->additionalProgramDirectories);
115
116         if (is_null($path) || !is_file($path)) {
117             throw new \Exception("Could not locate \"{$this->program}\" program.");
118         }
119
120         return $path;
121     }
122 }