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

ProcessExecutor   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 354
Duplicated Lines 4.8 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 15
dl 17
loc 354
rs 9.84
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A execute() 0 16 2
B executeCommand() 17 49 6
A executeTemplateRendering() 0 6 2
A getParsedShellCommand() 0 11 1
A setProcessDefaults() 0 6 1
A runProcess() 0 6 1
A testProcessResultValid() 0 6 2
A renderTemplate() 0 12 1
B waitForDeferredProcesses() 0 30 6
A deferProcess() 0 10 1
A isProcessResultValid() 0 4 2
A logWaitStart() 0 11 1
A logTemplateStart() 0 11 1
A logDeferedStart() 0 11 1
A logSynchronousProcessStart() 0 11 1
A logBashStart() 0 11 1
A logDeferredOutputStart() 0 11 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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