1
+ <?php
2
+
3
+ namespace Prokl \BitrixFixtureGeneratorBundle \Services \Runner ;
4
+
5
+ use Doctrine \Common \Collections \ArrayCollection ;
6
+ use Symfony \Component \Console \Helper \ProgressBar ;
7
+ use Symfony \Component \Console \Style \SymfonyStyle ;
8
+ use Symfony \Component \Lock \LockFactory ;
9
+ use Symfony \Component \Lock \LockInterface ;
10
+ use Symfony \Component \Lock \Store \FlockStore ;
11
+ use Symfony \Component \Process \PhpExecutableFinder ;
12
+ use Symfony \Component \Process \Process ;
13
+
14
+ /**
15
+ * Class CommandRunner
16
+ * @package Prokl\FrameworkExtensionBundle\Commands
17
+ *
18
+ * @since 02.04.2021
19
+ */
20
+ class CommandRunner
21
+ {
22
+ /**
23
+ * @var integer $limit
24
+ */
25
+ private $ limit = 5 ;
26
+
27
+ /**
28
+ * @var ArrayCollection $openProcesses
29
+ */
30
+ private $ openProcesses ;
31
+
32
+ /**
33
+ * @var boolean $active
34
+ */
35
+ private $ active = false ;
36
+
37
+ /**
38
+ * @var ArrayCollection $activeProcesses
39
+ */
40
+ private $ activeProcesses ;
41
+
42
+ /**
43
+ * @var ArrayCollection $completedProcesses
44
+ */
45
+ private $ completedProcesses ;
46
+
47
+ /**
48
+ * @var SymfonyStyle | null $io
49
+ */
50
+ private $ io ;
51
+
52
+ /**
53
+ * @var ProgressBar|null $progressBar
54
+ */
55
+ private $ progressBar ;
56
+
57
+ /**
58
+ * @var string $binary
59
+ */
60
+ private $ binary ;
61
+
62
+ /**
63
+ * @var string $subPath
64
+ */
65
+ private $ subPath ;
66
+
67
+ /**
68
+ * @var ArrayCollection $errors
69
+ */
70
+ private $ errors ;
71
+
72
+ /**
73
+ * @var boolean $continueOnError
74
+ */
75
+ private $ continueOnError = true ;
76
+
77
+ /**
78
+ * CommandRunner constructor.
79
+ *
80
+ * @param array $processes
81
+ * @param null|string $binary
82
+ */
83
+ public function __construct (array $ processes , ?string $ binary = null )
84
+ {
85
+ $ this ->openProcesses = new ArrayCollection ($ processes );
86
+ $ this ->activeProcesses = new ArrayCollection ();
87
+ $ this ->completedProcesses = new ArrayCollection ();
88
+ $ this ->errors = new ArrayCollection ();
89
+
90
+ $ finder = new PhpExecutableFinder ();
91
+ $ this ->subPath = $ _SERVER ['PHP_SELF ' ] ?? $ _SERVER ['SCRIPT_NAME ' ] ?? $ _SERVER ['SCRIPT_FILENAME ' ];
92
+ if ($ binary === null ) {
93
+ $ this ->setPhpBinary ((string )$ finder ->find ());
94
+ } else {
95
+ $ this ->setBinary ($ binary );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @param boolean $continue
101
+ *
102
+ * @return $this
103
+ */
104
+ public function continueOnError ($ continue = true ): self
105
+ {
106
+ $ this ->continueOnError = $ continue ;
107
+
108
+ return $ this ;
109
+ }
110
+
111
+ /**
112
+ * The lock handler only works if you're using just one server.
113
+ * If you have several hosts, you must not use this.
114
+ *
115
+ * @param string $command
116
+ * @param string $lockName
117
+ *
118
+ * @return LockInterface
119
+ */
120
+ public static function lock (string $ command , $ lockName = '' ): LockInterface
121
+ {
122
+ $ store = new FlockStore ();
123
+ $ factory = new LockFactory ($ store );
124
+ $ lock = $ factory ->createLock ($ command .$ lockName , 0 , true );
125
+ if (!$ lock ->acquire ()) {
126
+ exit (1 );
127
+ }
128
+
129
+ return $ lock ;
130
+ }
131
+
132
+ /**
133
+ * @return void
134
+ */
135
+ public function run (): void
136
+ {
137
+ $ this ->start ();
138
+ while ($ this ->hasOpenProcesses ()) {
139
+ if (!$ this ->process ()) {
140
+ break ;
141
+ }
142
+ usleep (5000 );
143
+ }
144
+ $ this ->finish ();
145
+ }
146
+
147
+ /**
148
+ * @return ArrayCollection
149
+ */
150
+ public function getErrors (): ArrayCollection
151
+ {
152
+ return $ this ->errors ;
153
+ }
154
+
155
+ /**
156
+ * @return boolean
157
+ */
158
+ public function hasOpenProcesses (): bool
159
+ {
160
+ return !$ this ->openProcesses ->isEmpty () || !$ this ->activeProcesses ->isEmpty ();
161
+ }
162
+
163
+ /**
164
+ * @return void
165
+ */
166
+ private function start (): void
167
+ {
168
+ $ this ->active = true ;
169
+ if ($ this ->io ) {
170
+ $ this ->createProgressBar ();
171
+ }
172
+ }
173
+
174
+ /**
175
+ * @param string $subPath
176
+ *
177
+ * @return $this
178
+ */
179
+ public function setSubPath (string $ subPath ): CommandRunner
180
+ {
181
+ $ this ->subPath = $ subPath ;
182
+
183
+ return $ this ;
184
+ }
185
+
186
+ /**
187
+ * @param SymfonyStyle $io
188
+ *
189
+ * @return $this
190
+ */
191
+ public function setIO (SymfonyStyle $ io ): CommandRunner
192
+ {
193
+ $ this ->io = $ io ;
194
+
195
+ return $ this ;
196
+ }
197
+
198
+ /**
199
+ * @return boolean
200
+ */
201
+ public function isActive (): bool
202
+ {
203
+ return $ this ->active ;
204
+ }
205
+
206
+ /**
207
+ * @param integer $limit
208
+ *
209
+ * @return $this
210
+ */
211
+ public function setLimit ($ limit = 5 ): CommandRunner
212
+ {
213
+ $ this ->limit = $ limit ;
214
+
215
+ return $ this ;
216
+ }
217
+
218
+ /**
219
+ * @param string $binary
220
+ *
221
+ * @return $this
222
+ */
223
+ public function setPhpBinary ($ binary ): CommandRunner
224
+ {
225
+ if (!$ binary ) {
226
+ if ($ this ->io !== null ) {
227
+ $ this ->io ->error ('Unable to find PHP binary. ' );
228
+ }
229
+ exit (500 );
230
+ }
231
+
232
+ $ this ->setBinary ($ binary );
233
+
234
+ return $ this ;
235
+ }
236
+
237
+ /**
238
+ * @param string $binary
239
+ *
240
+ * @return $this
241
+ */
242
+ public function setBinary ($ binary ): CommandRunner
243
+ {
244
+ $ this ->binary = $ binary ;
245
+
246
+ return $ this ;
247
+ }
248
+
249
+ /**
250
+ * Create styled progressbar.
251
+ *
252
+ * @return void
253
+ */
254
+ private function createProgressBar (): void
255
+ {
256
+ if ($ this ->io !== null ) {
257
+ $ progressBar = $ this ->io ->createProgressBar (count ($ this ->openProcesses ) * 2 );
258
+ $ progressBar ->setFormat ("%current%/%max% [%bar%] %percent:3s%% | %elapsed% \n%message% \n" );
259
+ $ progressBar ->setBarCharacter ('<fg=green>▓</> ' );
260
+ $ progressBar ->setEmptyBarCharacter ('<fg=red>░</> ' );
261
+ $ this ->progressBar = $ progressBar ;
262
+ $ this ->progressBar ->start ();
263
+ }
264
+ }
265
+
266
+ /**
267
+ * @return boolean
268
+ */
269
+ private function process (): bool
270
+ {
271
+ if ($ this ->activeProcesses ->count () < $ this ->limit ) {
272
+ $ this ->spawnNextProcess ();
273
+ }
274
+
275
+ return $ this ->validateRunningProcesses ();
276
+ }
277
+
278
+ /**
279
+ * Spawns next process
280
+ *
281
+ * @return void
282
+ */
283
+ private function spawnNextProcess (): void
284
+ {
285
+ if (!$ this ->openProcesses ->isEmpty ()) {
286
+ /** @var Process $process */
287
+ $ orgiginProcess = $ this ->openProcesses ->first ();
288
+ $ process = $ this ->modifyCommand ($ orgiginProcess );
289
+ $ this ->activeProcesses ->add ($ process );
290
+
291
+ if ($ this ->progressBar ) {
292
+ $ this ->progressBar ->setMessage ($ process ->getCommandLine ());
293
+ $ this ->progressBar ->display ();
294
+ }
295
+ $ process ->start ();
296
+ $ this ->openProcesses ->removeElement ($ orgiginProcess );
297
+
298
+ if ($ this ->progressBar ) {
299
+ $ this ->progressBar ->setProgress ($ this ->progressBar ->getProgress () + 1 );
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * @param Process $process
306
+ *
307
+ * @return Process
308
+ */
309
+ private function modifyCommand (Process $ process ): Process
310
+ {
311
+ return Process::fromShellCommandline (sprintf (
312
+ '%s %s %s ' ,
313
+ $ this ->binary ,
314
+ $ this ->subPath ,
315
+ $ process ->getCommandLine ()
316
+ ));
317
+ }
318
+
319
+ /**
320
+ * @return boolean
321
+ */
322
+ private function validateRunningProcesses (): bool
323
+ {
324
+ $ activeProcesses = $ this ->activeProcesses ;
325
+
326
+ /**
327
+ * @var string $key
328
+ * @var Process $activeProcess
329
+ */
330
+ foreach ($ activeProcesses as $ key => $ activeProcess ) {
331
+ if (!$ activeProcess ->isRunning ()) {
332
+ if ($ activeProcess ->getErrorOutput ()) {
333
+ $ this ->errors ->add ([
334
+ 'command ' => $ activeProcess ->getCommandLine (),
335
+ 'error ' => $ activeProcess ->getErrorOutput (),
336
+ ]);
337
+ if (!$ this ->continueOnError ) {
338
+ return false ;
339
+ }
340
+ }
341
+
342
+ $ this ->completedProcesses ->add ($ activeProcess );
343
+ $ this ->activeProcesses ->remove ($ key );
344
+ if ($ this ->progressBar ) {
345
+ $ this ->progressBar ->setProgress ($ this ->progressBar ->getProgress () + 1 );
346
+ }
347
+ }
348
+ usleep (5000 );
349
+ }
350
+
351
+ return true ;
352
+ }
353
+
354
+ /**
355
+ * @return void
356
+ */
357
+ private function finish () : void
358
+ {
359
+ if (!$ this ->errors ->isEmpty ()) {
360
+ foreach ($ this ->errors as $ error ) {
361
+ if ($ this ->io !== null ) {
362
+ $ this ->io ->warning ($ error );
363
+ }
364
+
365
+ }
366
+ }
367
+
368
+ $ this ->active = false ;
369
+ }
370
+ }
0 commit comments