Completed
Pull Request — master (#82)
by Jan Philipp
01:31
created

ProcessExecutor::execute()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 2
nc 3
nop 2
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
                    $this->testProcessResultValid($process, $command);
107
                } finally {
108
                    unlink($command->getScript()->getTmpPath());
109
                }
110
111
                break;
112 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...
113
                $parsedCommand = $this->getParsedShellCommand($command);
114
                $process = $this->environment->createProcess($parsedCommand);
115
                $this->setProcessDefaults($process, $command);
116
                $this->logSynchronousProcessStart($command, $index, $totalCount, $parsedCommand);
117
                $this->runProcess($process);
118
                $this->testProcessResultValid($process, $command);
119
120
                break;
121 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...
122
                $parsedCommand = $this->getParsedShellCommand($command);
123
                $process = $this->environment->createProcess($parsedCommand);
124
                $this->setProcessDefaults($process, $command);
125
                $this->logDeferedStart($command, $index, $totalCount, $parsedCommand);
126
                $this->deferProcess($parsedCommand, $command, $process);
127
128
                break;
129
            case $command instanceof TemplateCommand:
130
                $template = $command->createTemplate();
131
                $this->logTemplateStart($command, $index, $totalCount, $template);
132
                $this->renderTemplate($template);
133
134
                break;
135
            case $command instanceof WaitCommand:
136
                $this->logWaitStart($command, $index, $totalCount);
137
                $this->waitForDeferredProcesses();
138
139
                break;
140
        }
141
    }
142
143
    private function executeTemplateRendering()
144
    {
145
        foreach ($this->environment->getTemplates() as $template) {
146
            $this->renderTemplate($template);
147
        }
148
    }
149
150
    /**
151
     * @param ParsableCommand $command
152
     * @return string
153
     */
154
    private function getParsedShellCommand(ParsableCommand $command): string
155
    {
156
        $rawShellCommand = $command->getShellCommand();
157
158
        $parsedCommand = $this->templateEngine->render(
159
            $rawShellCommand,
160
            $this->environment->getAllValues()
161
        );
162
163
        return $parsedCommand;
164
    }
165
166
    /**
167
     * @param Process $process
168
     * @param ProcessCommand $command
169
     */
170
    private function setProcessDefaults(Process $process, ProcessCommand $command)
171
    {
172
        $process->setWorkingDirectory($this->applicationDirectory);
173
        $process->setTimeout(0);
174
        $process->setTty($command->isTTy());
175
    }
176
177
    /**
178
     * @param Process $process
179
     */
180
    private function runProcess(Process $process)
181
    {
182
        $process->run(function ($type, $response) {
183
            $this->logger->log(new LogMessage($response, $type === Process::ERR));
184
        });
185
    }
186
187
    /**
188
     * @param Process $process
189
     * @param ProcessCommand $command
190
     */
191
    private function testProcessResultValid(Process $process, ProcessCommand $command)
192
    {
193
        if (!$this->isProcessResultValid($process, $command)) {
194
            throw new ExecutionErrorException('Command exited with Error');
195
        }
196
    }
197
198
    /**
199
     * @param $template
200
     */
201
    private function renderTemplate(Template $template)
202
    {
203
        $renderedTemplateDestination = $this->templateEngine
204
            ->render($template->getDestination(), $this->environment->getAllValues());
205
206
        $template->setDestination($renderedTemplateDestination);
207
208
        $renderedTemplateContent = $this->templateEngine
209
            ->render($template->getContent(), $this->environment->getAllValues());
210
211
        $template->setContents($renderedTemplateContent);
212
    }
213
214
    private function waitForDeferredProcesses()
215
    {
216
        if (count($this->deferredProcesses) === 0) {
217
            return;
218
        }
219
220
        $this->logger->logWait();
221
222
        foreach ($this->deferredProcesses as $index => $deferredProcess) {
223
            $deferredProcess->getProcess()->wait();
224
225
            $this->logDeferredOutputStart($deferredProcess, $index);
226
227
            foreach ($deferredProcess->getLog() as $logMessage) {
228
                $this->logger->log($logMessage);
229
            }
230
231
            if ($this->isProcessResultValid($deferredProcess->getProcess(), $deferredProcess->getCommand())) {
232
                $this->logger->logSuccess();
233
            } else {
234
                $this->logger->logFailure();
235
            }
236
        }
237
238
        foreach ($this->deferredProcesses as $deferredProcess) {
239
            $this->testProcessResultValid($deferredProcess->getProcess(), $deferredProcess->getCommand());
240
        }
241
242
        $this->deferredProcesses = [];
243
    }
244
245
    /**
246
     * @param string $parsedCommand
247
     * @param DeferredProcessCommand $command
248
     * @param Process $process
249
     */
250
    private function deferProcess(string $parsedCommand, DeferredProcessCommand $command, Process $process)
251
    {
252
        $deferredProcess = new DeferredProcess($parsedCommand, $command, $process);
253
254
        $process->start(function ($type, $response) use ($deferredProcess) {
255
            $deferredProcess->log(new LogMessage($response, $type === Process::ERR));
256
        });
257
258
        $this->deferredProcesses[] = $deferredProcess;
259
    }
260
261
    /**
262
     * @param Process $process
263
     * @param bool $ignoreError
0 ignored issues
show
Bug introduced by
There is no parameter named $ignoreError. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
264
     * @return bool
265
     */
266
    private function isProcessResultValid(Process $process, ProcessCommand $command): bool
267
    {
268
        return $command->isIgnoreError() || $process->isSuccessful();
269
    }
270
271
    /**
272
     * @param WaitCommand $command
273
     * @param int $index
274
     * @param int $totalCount
275
     */
276
    private function logWaitStart(WaitCommand $command, int $index, int $totalCount)
277
    {
278
        $this->logger->logStart(
279
            'Waiting',
280
            '',
281
            $command->getLineNumber(),
282
            false,
283
            $index,
284
            $totalCount
285
        );
286
    }
287
288
    /**
289
     * @param TemplateCommand $command
290
     * @param int $index
291
     * @param int $totalCount
292
     * @param Template $template
293
     */
294
    private function logTemplateStart(TemplateCommand $command, int $index, int $totalCount, Template $template)
295
    {
296
        $this->logger->logStart(
297
            'Template',
298
            $template->getDestination(),
299
            $command->getLineNumber(),
300
            false,
301
            $index,
302
            $totalCount
303
        );
304
    }
305
306
    /**
307
     * @param DeferredProcessCommand $command
308
     * @param int $index
309
     * @param int $totalCount
310
     * @param string $parsedCommand
311
     */
312
    private function logDeferedStart(DeferredProcessCommand $command, int $index, int $totalCount, string $parsedCommand)
313
    {
314
        $this->logger->logStart(
315
            'Defering',
316
            $parsedCommand,
317
            $command->getLineNumber(),
318
            $command->isIgnoreError(),
319
            $index,
320
            $totalCount
321
        );
322
    }
323
324
    /**
325
     * @param ProcessCommand $command
326
     * @param int $index
327
     * @param int $totalCount
328
     * @param string $parsedCommand
329
     */
330
    private function logSynchronousProcessStart(ProcessCommand $command, int $index, int $totalCount, string $parsedCommand)
331
    {
332
        $this->logger->logStart(
333
            'Starting',
334
            $parsedCommand,
335
            $command->getLineNumber(),
336
            $command->isIgnoreError(),
337
            $index,
338
            $totalCount
339
        );
340
    }
341
342
    /**
343
     * @param BashCommand $command
344
     * @param int $index
345
     * @param int $totalCount
346
     */
347
    private function logBashStart(BashCommand $command, int $index, int $totalCount)
348
    {
349
        $this->logger->logStart(
350
            'Executing',
351
            $command->getScript()->getPath(),
352
            $command->getLineNumber(),
353
            false,
354
            $index,
355
            $totalCount
356
        );
357
    }
358
359
    /**
360
     * @param DeferredProcess $deferredProcess
361
     * @param $index
362
     */
363
    private function logDeferredOutputStart(DeferredProcess $deferredProcess, $index)
364
    {
365
        $this->logger->logStart(
366
            'Output from',
367
            $deferredProcess->getParsedCommand(),
368
            $deferredProcess->getCommand()->getLineNumber(),
369
            $deferredProcess->getCommand()->isIgnoreError(),
370
            $index,
371
            count($this->deferredProcesses)
372
        );
373
    }
374
}
375