Completed
Push — master ( 1ece6c...656998 )
by Jan Philipp
13s queued 11s
created

ProcessExecutor::waitForDeferredProcesses()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 8.8177
c 0
b 0
f 0
cc 6
nc 11
nop 0
1
<?php declare(strict_types=1);
2
3
4
namespace Shopware\Psh\ScriptRuntime\Execution;
5
6
use Shopware\Psh\Listing\Script;
7
use Shopware\Psh\ScriptRuntime\BashCommand;
8
use Shopware\Psh\ScriptRuntime\Command;
9
use Shopware\Psh\ScriptRuntime\DeferredProcessCommand;
10
use Shopware\Psh\ScriptRuntime\ParsableCommand;
11
use Shopware\Psh\ScriptRuntime\ProcessCommand;
12
use Shopware\Psh\ScriptRuntime\SynchronusProcessCommand;
13
use Shopware\Psh\ScriptRuntime\TemplateCommand;
14
use Shopware\Psh\ScriptRuntime\WaitCommand;
15
use Symfony\Component\Process\Process;
16
17
/**
18
 * Execute a command in a separate process
19
 */
20
class ProcessExecutor
21
{
22
    /**
23
     * @var ProcessEnvironment
24
     */
25
    private $environment;
26
27
    /**
28
     * @var TemplateEngine
29
     */
30
    private $templateEngine;
31
32
    /**
33
     * @var Logger
34
     */
35
    private $logger;
36
37
    /**
38
     * @var string
39
     */
40
    private $applicationDirectory;
41
42
    /**
43
     * @var DeferredProcess[]
44
     */
45
    private $deferredProcesses = [];
46
47
    /**
48
     * ProcessExecutor constructor.
49
     * @param ProcessEnvironment $environment
50
     * @param TemplateEngine $templateEngine
51
     * @param Logger $logger
52
     * @param string $applicationDirectory
53
     */
54
    public function __construct(
55
        ProcessEnvironment $environment,
56
        TemplateEngine $templateEngine,
57
        Logger $logger,
58
        string $applicationDirectory
59
    ) {
60
        $this->environment = $environment;
61
        $this->templateEngine = $templateEngine;
62
        $this->logger = $logger;
63
        $this->applicationDirectory = $applicationDirectory;
64
    }
65
66
    /**
67
     * @param Script $script
68
     * @param Command[] $commands
69
     */
70
    public function execute(Script $script, array $commands)
71
    {
72
        $this->logger->startScript($script);
73
74
        $this->executeTemplateRendering();
75
76
        try {
77
            foreach ($commands as $index => $command) {
78
                $this->executeCommand($command, $index, count($commands));
79
            }
80
        } finally {
81
            $this->waitForDeferredProcesses();
82
        }
83
84
        $this->logger->finishScript($script);
85
    }
86
87
    /**
88
     * @param Command $command
89
     * @param int $index
90
     * @param int $totalCount
91
     */
92
    private function executeCommand(Command $command, int $index, int $totalCount)
93
    {
94
        switch (true) {
95
            case $command instanceof BashCommand:
96
                $originalContent = file_get_contents($command->getScript()->getPath());
97
98
                try {
99
                    file_put_contents($command->getScript()->getTmpPath(), $this->templateEngine->render($originalContent, $this->environment->getAllValues()));
100
                    chmod($command->getScript()->getTmpPath(), 0700);
101
102
                    $process = $this->environment->createProcess($command->getScript()->getTmpPath());
103
                    $this->setProcessDefaults($process, $command);
104
                    $this->logBashStart($command, $index, $totalCount);
105
                    $this->runProcess($process);
106
107
                    if ($command->hasWarning()) {
108
                        $this->logger->warn($command->getWarning());
109
                    }
110
111
                    $this->testProcessResultValid($process, $command);
112
                } finally {
113
                    unlink($command->getScript()->getTmpPath());
114
                }
115
116
                break;
117 View Code Duplication
            case $command instanceof SynchronusProcessCommand:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
                $parsedCommand = $this->getParsedShellCommand($command);
119
                $process = $this->environment->createProcess($parsedCommand);
120
                $this->setProcessDefaults($process, $command);
121
                $this->logSynchronousProcessStart($command, $index, $totalCount, $parsedCommand);
122
                $this->runProcess($process);
123
                $this->testProcessResultValid($process, $command);
124
125
                break;
126 View Code Duplication
            case $command instanceof DeferredProcessCommand:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
                $parsedCommand = $this->getParsedShellCommand($command);
128
                $process = $this->environment->createProcess($parsedCommand);
129
                $this->setProcessDefaults($process, $command);
130
                $this->logDeferedStart($command, $index, $totalCount, $parsedCommand);
131
                $this->deferProcess($parsedCommand, $command, $process);
132
133
                break;
134
            case $command instanceof TemplateCommand:
135
                $template = $command->createTemplate();
136
                $this->logTemplateStart($command, $index, $totalCount, $template);
137
                $this->renderTemplate($template);
138
139
                break;
140
            case $command instanceof WaitCommand:
141
                $this->logWaitStart($command, $index, $totalCount);
142
                $this->waitForDeferredProcesses();
143
144
                break;
145
        }
146
    }
147
148
    private function executeTemplateRendering()
149
    {
150
        foreach ($this->environment->getTemplates() as $template) {
151
            $this->renderTemplate($template);
152
        }
153
    }
154
155
    /**
156
     * @param ParsableCommand $command
157
     * @return string
158
     */
159
    private function getParsedShellCommand(ParsableCommand $command): string
160
    {
161
        $rawShellCommand = $command->getShellCommand();
162
163
        $parsedCommand = $this->templateEngine->render(
164
            $rawShellCommand,
165
            $this->environment->getAllValues()
166
        );
167
168
        return $parsedCommand;
169
    }
170
171
    /**
172
     * @param Process $process
173
     * @param ProcessCommand $command
174
     */
175
    private function setProcessDefaults(Process $process, ProcessCommand $command)
176
    {
177
        $process->setWorkingDirectory($this->applicationDirectory);
178
        $process->setTimeout(0);
179
        $process->setTty($command->isTTy());
180
    }
181
182
    /**
183
     * @param Process $process
184
     */
185
    private function runProcess(Process $process)
186
    {
187
        $process->run(function ($type, $response) {
188
            $this->logger->log(new LogMessage($response, $type === Process::ERR));
189
        });
190
    }
191
192
    /**
193
     * @param Process $process
194
     * @param ProcessCommand $command
195
     */
196
    private function testProcessResultValid(Process $process, ProcessCommand $command)
197
    {
198
        if (!$this->isProcessResultValid($process, $command)) {
199
            throw new ExecutionErrorException('Command exited with Error');
200
        }
201
    }
202
203
    /**
204
     * @param $template
205
     */
206
    private function renderTemplate(Template $template)
207
    {
208
        $renderedTemplateDestination = $this->templateEngine
209
            ->render($template->getDestination(), $this->environment->getAllValues());
210
211
        $template->setDestination($renderedTemplateDestination);
212
213
        $renderedTemplateContent = $this->templateEngine
214
            ->render($template->getContent(), $this->environment->getAllValues());
215
216
        $template->setContents($renderedTemplateContent);
217
    }
218
219
    private function waitForDeferredProcesses()
220
    {
221
        if (count($this->deferredProcesses) === 0) {
222
            return;
223
        }
224
225
        $this->logger->logWait();
226
227
        foreach ($this->deferredProcesses as $index => $deferredProcess) {
228
            $deferredProcess->getProcess()->wait();
229
230
            $this->logDeferredOutputStart($deferredProcess, $index);
231
232
            foreach ($deferredProcess->getLog() as $logMessage) {
233
                $this->logger->log($logMessage);
234
            }
235
236
            if ($this->isProcessResultValid($deferredProcess->getProcess(), $deferredProcess->getCommand())) {
237
                $this->logger->logSuccess();
238
            } else {
239
                $this->logger->logFailure();
240
            }
241
        }
242
243
        foreach ($this->deferredProcesses as $deferredProcess) {
244
            $this->testProcessResultValid($deferredProcess->getProcess(), $deferredProcess->getCommand());
245
        }
246
247
        $this->deferredProcesses = [];
248
    }
249
250
    /**
251
     * @param string $parsedCommand
252
     * @param DeferredProcessCommand $command
253
     * @param Process $process
254
     */
255
    private function deferProcess(string $parsedCommand, DeferredProcessCommand $command, Process $process)
256
    {
257
        $deferredProcess = new DeferredProcess($parsedCommand, $command, $process);
258
259
        $process->start(function ($type, $response) use ($deferredProcess) {
260
            $deferredProcess->log(new LogMessage($response, $type === Process::ERR));
261
        });
262
263
        $this->deferredProcesses[] = $deferredProcess;
264
    }
265
266
    /**
267
     * @param Process $process
268
     * @param ProcessCommand $command
269
     * @return bool
270
     */
271
    private function isProcessResultValid(Process $process, ProcessCommand $command): bool
272
    {
273
        return $command->isIgnoreError() || $process->isSuccessful();
274
    }
275
276
    /**
277
     * @param WaitCommand $command
278
     * @param int $index
279
     * @param int $totalCount
280
     */
281
    private function logWaitStart(WaitCommand $command, int $index, int $totalCount)
282
    {
283
        $this->logger->logStart(
284
            'Waiting',
285
            '',
286
            $command->getLineNumber(),
287
            false,
288
            $index,
289
            $totalCount
290
        );
291
    }
292
293
    /**
294
     * @param TemplateCommand $command
295
     * @param int $index
296
     * @param int $totalCount
297
     * @param Template $template
298
     */
299
    private function logTemplateStart(TemplateCommand $command, int $index, int $totalCount, Template $template)
300
    {
301
        $this->logger->logStart(
302
            'Template',
303
            $template->getDestination(),
304
            $command->getLineNumber(),
305
            false,
306
            $index,
307
            $totalCount
308
        );
309
    }
310
311
    /**
312
     * @param DeferredProcessCommand $command
313
     * @param int $index
314
     * @param int $totalCount
315
     * @param string $parsedCommand
316
     */
317
    private function logDeferedStart(DeferredProcessCommand $command, int $index, int $totalCount, string $parsedCommand)
318
    {
319
        $this->logger->logStart(
320
            'Deferring',
321
            $parsedCommand,
322
            $command->getLineNumber(),
323
            $command->isIgnoreError(),
324
            $index,
325
            $totalCount
326
        );
327
    }
328
329
    /**
330
     * @param ProcessCommand $command
331
     * @param int $index
332
     * @param int $totalCount
333
     * @param string $parsedCommand
334
     */
335
    private function logSynchronousProcessStart(ProcessCommand $command, int $index, int $totalCount, string $parsedCommand)
336
    {
337
        $this->logger->logStart(
338
            'Starting',
339
            $parsedCommand,
340
            $command->getLineNumber(),
341
            $command->isIgnoreError(),
342
            $index,
343
            $totalCount
344
        );
345
    }
346
347
    /**
348
     * @param BashCommand $command
349
     * @param int $index
350
     * @param int $totalCount
351
     */
352
    private function logBashStart(BashCommand $command, int $index, int $totalCount)
353
    {
354
        $this->logger->logStart(
355
            'Executing',
356
            $command->getScript()->getPath(),
357
            $command->getLineNumber(),
358
            false,
359
            $index,
360
            $totalCount
361
        );
362
    }
363
364
    /**
365
     * @param DeferredProcess $deferredProcess
366
     * @param $index
367
     */
368
    private function logDeferredOutputStart(DeferredProcess $deferredProcess, $index)
369
    {
370
        $this->logger->logStart(
371
            'Output from',
372
            $deferredProcess->getParsedCommand(),
373
            $deferredProcess->getCommand()->getLineNumber(),
374
            $deferredProcess->getCommand()->isIgnoreError(),
375
            $index,
376
            count($this->deferredProcesses)
377
        );
378
    }
379
}
380