Skip to content

Commit 7e4e5d8

Browse files
committed
fixes
1 parent f58cf17 commit 7e4e5d8

File tree

5 files changed

+451
-3
lines changed

5 files changed

+451
-3
lines changed

Command/StructureCreatorRunner.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Prokl\BitrixFixtureGeneratorBundle\Command;
44

55
use Exception;
6-
use Local\Commands\Runner\CommandRunner;
6+
use Prokl\BitrixFixtureGeneratorBundle\Services\Runner\CommandRunner;
77
use Symfony\Component\Console\Command\Command;
88
use Symfony\Component\Console\Input\InputInterface;
99
use Symfony\Component\Console\Output\OutputInterface;
@@ -69,6 +69,5 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6969
->run();
7070

7171
return 0;
72-
7372
}
7473
}

Services/Runner/CommandRunner.php

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
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

Comments
 (0)