Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
56 / 56
ProcessHandler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
11 / 11
25
100.00% covered (success)
100.00%
56 / 56
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 addOptions
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 run
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 start
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 startDetached
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 isRunning
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 getKey
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getInfos
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
10 / 10
 setEnv
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 createProcess
100.00% covered (success)
100.00%
1 / 1
10
100.00% covered (success)
100.00%
18 / 18
 defaultCallback
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
1<?php
2
3namespace Qmp\Laravel\AsyncProcessFactory;
4
5use Closure;
6use Illuminate\Support\Collection;
7use Illuminate\Support\Str;
8use Qmp\Laravel\AsyncProcessFactory\Exceptions\SubProcessException;
9use Symfony\Component\Process\Process;
10
11class ProcessHandler
12{
13    /**
14     * The current Process
15     *
16     * @var Symfony\Component\Process\Process
17     */
18    protected $process;
19
20    /**
21     * The current command
22     *
23     * @var string
24     */
25    protected $command;
26
27    /**
28     * Wether the process is detached or not
29     *
30     * @var boolean
31     */
32    protected $detached = false;
33
34    /**
35     * The current command options
36     *
37     * @var array
38     */
39    protected $options = [];
40
41    /**
42     * The current callback callback
43     *
44     * @var callable
45     */
46    protected $callback = null;
47
48    /**
49     * The current process key in the stack
50     *
51     * @var string
52     */
53    protected $key;
54
55    /**
56     * Undocumented variable
57     *
58     * @var Collection
59     */
60    protected $envs;
61
62
63    /**
64     * Create a new process Handler
65     *
66     * @param string $command
67     * @param string $key
68     */
69    public function __construct(string $command)
70    {
71        $this->command = $command;
72        $this->key = bin2hex(random_bytes(6));
73    }
74
75    /**
76     * Add options to process
77     *
78     * @param array $options
79     * @return Qmp\Laravel\AsyncProcessFactory\ProcessHandler
80     */
81    public function addOptions(array $options): ProcessHandler
82    {
83        $this->options = $options;
84
85        return $this;
86    }
87
88    /**
89     * Run Process one by one
90     *
91     * @param callable $callback
92     * @return void
93     */
94    public function run(callable $callback = null): void
95    {
96        $this->callback = $callback;
97        $this->createProcess()->run(Closure::fromCallable([$this, 'defaultCallback']));
98    }
99
100    /**
101     * Start Process in parralel
102     *
103     * @param callable $callback
104     * @return void
105     */
106    public function start(callable $callback = null): void
107    {
108        $this->callback = $callback;
109        $this->createProcess()->start(Closure::fromCallable([$this, 'defaultCallback']));
110    }
111
112    /**
113     * Start Process in parralel
114     *
115     * @param callable $callback
116     * @return boolean
117     */
118    public function startDetached(): bool
119    {
120        $this->detached = true;
121        $this->createProcess()->start();
122
123        return $this->process->waitUntil(function ($type, $datas) {
124            return trim($datas) === 'process started';
125        });
126    }
127
128    /**
129     * Check if the current Process isrunning
130     *
131     * @return boolean
132     */
133    public function isRunning(): bool
134    {
135        if ($this->process) {
136            return $this->process->isRunning();
137        }
138        return false;
139    }
140
141    /**
142     * Return the current process key
143     *
144     * @return string
145     */
146    public function getKey(): string
147    {
148        return $this->key;
149    }
150
151    /**
152     * Return the current process informations
153     *
154     * @param bool|null $started
155     * @return Collection
156     */
157    public function getInfos($started = null): Collection
158    {
159        $infos = collect([
160            'command' => $this->command,
161            'options' => collect($this->options),
162            'pid' => $this->process ? $this->process->getPid() : null,
163            'task' => $this->key,
164            'detached' => $this->detached,
165            'envs' => $this->envs
166        ]);
167
168        if ($started) {
169            $infos->put('started', $started);
170        }
171
172        return $infos;
173    }
174
175    /**
176     * Set environment vars
177     *
178     * @param Collection $envs
179     * @return self
180     */
181    public function setEnv(Collection $envs)
182    {
183        $this->envs = $envs;
184        return $this;
185    }
186
187    /**
188     * Create and return a new process
189     *
190     * @return Process
191     */
192    protected function createProcess(): Process
193    {
194        $options = collect($this->options)
195            ->map(function ($value, $option) {
196                if (is_bool($value) && Str::startsWith($option, '*')) {
197                    if ($value) {
198                        return "--" . str_replace('*', '', $option);
199                    }
200                    return '';
201                }
202                $value = is_bool($value) ? ($value ? 'true' : 'false') : $value;
203                return is_numeric($option) ? $value : "--$option=$value";
204            });
205
206        if ($this->detached) {
207            $options->push('&');
208        }
209
210        $envs = "";
211
212        if ($this->envs && $this->envs->count()) {
213            $envs = $this->envs->implodePair("=", " ") . " ";
214        }
215
216        $cmd = 'php ' . base_path('artisan') . " $this->command " . trim($options->implode(' '));
217        $this->process = Process::fromShellCommandline($envs . $cmd);
218
219        $this->process->setTimeout(0);
220
221        return $this->process;
222    }
223
224    /**
225     * The default callback for stdout/stderr
226     *
227     * @param string $type
228     * @param string $datas
229     * @return void
230     */
231    protected function defaultCallback(string $type, string $datas): void
232    {
233        if (strtolower($type) === "err") {
234            throw new SubProcessException($datas, $this->key);
235        }
236
237        if ($this->callback) {
238            $callback = $this->callback;
239            $callback($datas);
240        }
241    }
242}