Completed
Push — master ( 9d2c7e...85264a )
by Jan Philipp
11s
created

ProcessExecutor::testProcessResultValid()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 3
nc 2
nop 2
1
<?php declare(strict_types=1);
2
3
4
namespace Shopware\Psh\ScriptRuntime;
5
6
use Shopware\Psh\Listing\Script;
7
use Symfony\Component\Process\Process;
8
9
/**
10
 * Execute a command in a separate process
11
 */
12
class ProcessExecutor
13
{
14
    /**
15
     * @var ProcessEnvironment
16
     */
17
    private $environment;
18
19
    /**
20
     * @var TemplateEngine
21
     */
22
    private $templateEngine;
23
24
    /**
25
     * @var Logger
26
     */
27
    private $logger;
28
29
    /**
30
     * @var string
31
     */
32
    private $applicationDirectory;
33
34
    /**
35
     * ProcessExecutor constructor.
36
     * @param ProcessEnvironment $environment
37
     * @param TemplateEngine $templateEngine
38
     * @param Logger $logger
39
     * @param string $applicationDirectory
40
     */
41
    public function __construct(
42
        ProcessEnvironment $environment,
43
        TemplateEngine $templateEngine,
44
        Logger $logger,
45
        string $applicationDirectory
46
    ) {
47
        $this->environment = $environment;
48
        $this->templateEngine = $templateEngine;
49
        $this->logger = $logger;
50
        $this->applicationDirectory = $applicationDirectory;
51
    }
52
53
    /**
54
     * @param Script $script
55
     * @param Command[] $commands
56
     */
57
    public function execute(Script $script, array $commands)
58
    {
59
        $this->logger->startScript($script);
60
61
        $this->executeTemplateRendering();
62
63
        foreach ($commands as $index => $command) {
64
            switch ($command) {
65
                case $command instanceof ProcessCommand:
66
                    $parsedCommand = $this->getParsedShellCommand($command);
0 ignored issues
show
Compatibility introduced by
$command of type object<Shopware\Psh\ScriptRuntime\Command> is not a sub-type of object<Shopware\Psh\ScriptRuntime\ProcessCommand>. It seems like you assume a concrete implementation of the interface Shopware\Psh\ScriptRuntime\Command to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
67
68
                    $this->logger->logCommandStart(
69
                        $parsedCommand,
70
                        $command->getLineNumber(),
71
                        $command->isIgnoreError(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Shopware\Psh\ScriptRuntime\Command as the method isIgnoreError() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\ProcessCommand.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
72
                        $index,
73
                        count($commands)
74
                    );
75
76
                    $process = $this->environment->createProcess($parsedCommand);
77
78
                    $this->setUpProcess($command, $process);
0 ignored issues
show
Compatibility introduced by
$command of type object<Shopware\Psh\ScriptRuntime\Command> is not a sub-type of object<Shopware\Psh\ScriptRuntime\ProcessCommand>. It seems like you assume a concrete implementation of the interface Shopware\Psh\ScriptRuntime\Command to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
79
                    $this->runProcess($process);
80
                    $this->testProcessResultValid($command, $process);
0 ignored issues
show
Compatibility introduced by
$command of type object<Shopware\Psh\ScriptRuntime\Command> is not a sub-type of object<Shopware\Psh\ScriptRuntime\ProcessCommand>. It seems like you assume a concrete implementation of the interface Shopware\Psh\ScriptRuntime\Command to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
81
                    break;
82
                case $command instanceof TemplateCommand:
83
                    $template = $command->getTemplate();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Shopware\Psh\ScriptRuntime\Command as the method getTemplate() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\TemplateCommand.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
84
85
                    $this->logger->logTemplate(
86
                        $template->getDestination(),
87
                        $command->getLineNumber(),
88
                        $index,
89
                        count($commands)
90
                    );
91
92
                    $this->renderTemplate($template);
93
                break;
94
            }
95
        }
96
97
        $this->logger->finishScript($script);
98
    }
99
100
    private function executeTemplateRendering()
101
    {
102
        foreach ($this->environment->getTemplates() as $template) {
103
            $this->renderTemplate($template);
104
        }
105
    }
106
107
    /**
108
     * @param ProcessCommand $command
109
     * @return string
110
     */
111
    protected function getParsedShellCommand(ProcessCommand $command): string
112
    {
113
        $rawShellCommand = $command->getShellCommand();
114
115
        $parsedCommand = $this->templateEngine->render(
116
            $rawShellCommand,
117
            $this->environment->getAllValues()
118
        );
119
120
        return $parsedCommand;
121
    }
122
123
    /**
124
     * @param Process $process
125
     */
126
    protected function setUpProcess(ProcessCommand $command, Process $process)
127
    {
128
        $process->setWorkingDirectory($this->applicationDirectory);
129
        $process->setTimeout(0);
130
        $process->setTty($command->isTTy());
131
    }
132
133
    /**
134
     * @param Process $process
135
     */
136
    protected function runProcess(Process $process)
137
    {
138
        $process->run(function ($type, $response) {
139
            if (Process::ERR === $type) {
140
                $this->logger->err($response);
141
            } else {
142
                $this->logger->out($response);
143
            }
144
        });
145
    }
146
147
    /**
148
     * @param ProcessCommand $command
149
     * @param Process $process
150
     */
151
    protected function testProcessResultValid(ProcessCommand $command, Process $process)
152
    {
153
        if (!$command->isIgnoreError() && !$process->isSuccessful()) {
154
            throw new ExecutionErrorException('Command exited with Error');
155
        }
156
    }
157
158
    /**
159
     * @param $template
160
     */
161
    private function renderTemplate(Template $template)
162
    {
163
        $renderedTemplateDestination = $this->templateEngine
164
            ->render($template->getDestination(), $this->environment->getAllValues());
165
166
        $template->setDestination($renderedTemplateDestination);
167
168
        $renderedTemplateContent = $this->templateEngine
169
            ->render($template->getContent(), $this->environment->getAllValues());
170
171
        $template->setContents($renderedTemplateContent);
172
    }
173
}
174