Completed
Pull Request — master (#82)
by Jan Philipp
01:36
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\ProcessCommand;
11
use Shopware\Psh\ScriptRuntime\SynchronusProcessCommand;
12
use Shopware\Psh\ScriptRuntime\TemplateCommand;
13
use Shopware\Psh\ScriptRuntime\WaitCommand;
14
use Symfony\Component\Process\Process;
15
16
/**
17
 * Execute a command in a separate process
18
 */
19
class ProcessExecutor
20
{
21
    /**
22
     * @var ProcessEnvironment
23
     */
24
    private $environment;
25
26
    /**
27
     * @var TemplateEngine
28
     */
29
    private $templateEngine;
30
31
    /**
32
     * @var Logger
33
     */
34
    private $logger;
35
36
    /**
37
     * @var string
38
     */
39
    private $applicationDirectory;
40
41
    /**
42
     * @var DeferredProcess[]
43
     */
44
    private $deferredProcesses = [];
45
46
    /**
47
     * ProcessExecutor constructor.
48
     * @param ProcessEnvironment $environment
49
     * @param TemplateEngine $templateEngine
50
     * @param Logger $logger
51
     * @param string $applicationDirectory
52
     */
53
    public function __construct(
54
        ProcessEnvironment $environment,
55
        TemplateEngine $templateEngine,
56
        Logger $logger,
57
        string $applicationDirectory
58
    ) {
59
        $this->environment = $environment;
60
        $this->templateEngine = $templateEngine;
61
        $this->logger = $logger;
62
        $this->applicationDirectory = $applicationDirectory;
63
    }
64
65
    /**
66
     * @param Script $script
67
     * @param Command[] $commands
68
     */
69
    public function execute(Script $script, array $commands)
70
    {
71
        $this->logger->startScript($script);
72
73
        $this->executeTemplateRendering();
74
75
        try {
76
            foreach ($commands as $index => $command) {
77
                $this->executeCommand($command, $index, count($commands));
78
            }
79
        } finally {
80
            $this->waitForDeferredProcesses();
81
        }
82
83
        $this->logger->finishScript($script);
84
    }
85
86
    /**
87
     * @param Command $command
88
     * @param int $index
89
     * @param int $totalCount
90
     */
91
    private function executeCommand(Command $command, int $index, int $totalCount)
92
    {
93
        switch ($command) {
94
            case $command instanceof BashCommand:
95
                $originalContent = file_get_contents($command->getScript()->getPath());
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 getScript() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\BashCommand.

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...
96
97
                try {
98
                    file_put_contents($command->getScript()->getPath(), $this->templateEngine->render($originalContent, $this->environment->getAllValues()));
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 getScript() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\BashCommand.

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...
99
100
                    $process = $this->environment->createProcess($command->getScript()->getPath());
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 getScript() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\BashCommand.

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...
101
                    $this->setProcessDefaults($process, false);
102
103
                    $this->logger->logStart(
104
                        'Executing',
105
                        $command->getScript()->getPath(),
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 getScript() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\BashCommand.

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...
106
                        $command->getLineNumber(),
107
                        false,
108
                        $index,
109
                        $totalCount
110
                    );
111
112
                    $this->runProcess($process);
113
                    $this->testProcessResultValid($process, false);
114
                } finally {
115
                    file_put_contents($command->getScript()->getPath(), $originalContent);
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 getScript() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\BashCommand.

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...
116
                }
117
118
119
                break;
120 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...
121
                $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 child interface 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...
122
                $process = $this->environment->createProcess($parsedCommand);
123
                $this->setProcessDefaults($process, $command->isTTy());
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 isTTy() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\DeferredProcessCommand, Shopware\Psh\ScriptRunti...ynchronusProcessCommand.

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...
124
125
                $this->logger->logStart(
126
                    'Starting',
127
                    $parsedCommand,
128
                    $command->getLineNumber(),
129
                    $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\DeferredProcessCommand, Shopware\Psh\ScriptRunti...ynchronusProcessCommand.

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...
130
                    $index,
131
                    $totalCount
132
                );
133
134
                $this->runProcess($process);
135
                $this->testProcessResultValid($process, $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\DeferredProcessCommand, Shopware\Psh\ScriptRunti...ynchronusProcessCommand.

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...
136
137
                break;
138
139
140 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...
141
                $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 child interface 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...
142
                $process = $this->environment->createProcess($parsedCommand);
143
                $this->setProcessDefaults($process, $command->isTTy());
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 isTTy() does only exist in the following implementations of said interface: Shopware\Psh\ScriptRuntime\DeferredProcessCommand, Shopware\Psh\ScriptRunti...ynchronusProcessCommand.

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...
144
145
                $this->logger->logStart(
146
                    'Defering',
147
                    $parsedCommand,
148
                    $command->getLineNumber(),
149
                    $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\DeferredProcessCommand, Shopware\Psh\ScriptRunti...ynchronusProcessCommand.

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...
150
                    $index,
151
                    $totalCount
152
                );
153
                $this->deferProcess($parsedCommand, $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\Scri...DeferredProcessCommand>. 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...
154
155
                break;
156
157
158
            case $command instanceof TemplateCommand:
159
                    $template = $command->createTemplate();
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 createTemplate() 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...
160
161
                $this->logger->logStart(
162
                    'Template',
163
                    $template->getDestination(),
164
                    $command->getLineNumber(),
165
                    false,
166
                    $index,
167
                    $totalCount
168
                );
169
170
                $this->renderTemplate($template);
171
                break;
172
173
            case $command instanceof WaitCommand:
174
                $this->logger->logStart(
175
                    'Waiting',
176
                    '',
177
                    $command->getLineNumber(),
178
                    false,
179
                    $index,
180
                    $totalCount
181
                );
182
183
                $this->waitForDeferredProcesses();
184
                break;
185
        }
186
    }
187
188
    private function executeTemplateRendering()
189
    {
190
        foreach ($this->environment->getTemplates() as $template) {
191
            $this->renderTemplate($template);
192
        }
193
    }
194
195
    /**
196
     * @param ProcessCommand $command
197
     * @return string
198
     */
199
    protected function getParsedShellCommand(ProcessCommand $command): string
200
    {
201
        $rawShellCommand = $command->getShellCommand();
202
203
        $parsedCommand = $this->templateEngine->render(
204
            $rawShellCommand,
205
            $this->environment->getAllValues()
206
        );
207
208
        return $parsedCommand;
209
    }
210
211
    /**
212
     * @param Process $process
213
     * @param bool $isTty
214
     */
215
    private function setProcessDefaults(Process $process, bool $isTty)
216
    {
217
        $process->setWorkingDirectory($this->applicationDirectory);
218
        $process->setTimeout(0);
219
        $process->setTty($isTty);
220
    }
221
222
    /**
223
     * @param Process $process
224
     */
225
    private function runProcess(Process $process)
226
    {
227
        $process->run(function ($type, $response) {
228
            $this->logger->log(new LogMessage($response, $type === Process::ERR));
229
        });
230
    }
231
232
    /**
233
     * @param Process $process
234
     * @param bool $ignoreError
235
     */
236
    protected function testProcessResultValid(Process $process, bool $ignoreError)
237
    {
238
        if (!$this->isProcessResultValid($process, $ignoreError)) {
239
            throw new ExecutionErrorException('Command exited with Error');
240
        }
241
    }
242
243
    /**
244
     * @param $template
245
     */
246
    private function renderTemplate(Template $template)
247
    {
248
        $renderedTemplateDestination = $this->templateEngine
249
            ->render($template->getDestination(), $this->environment->getAllValues());
250
251
        $template->setDestination($renderedTemplateDestination);
252
253
        $renderedTemplateContent = $this->templateEngine
254
            ->render($template->getContent(), $this->environment->getAllValues());
255
256
        $template->setContents($renderedTemplateContent);
257
    }
258
259
    private function waitForDeferredProcesses()
260
    {
261
        if (count($this->deferredProcesses) === 0) {
262
            return;
263
        }
264
265
        $this->logger->logWait();
266
267
        foreach ($this->deferredProcesses as $index => $deferredProcess) {
268
            $deferredProcess->getProcess()->wait();
269
270
            $this->logger->logStart(
271
                'Output from',
272
                $deferredProcess->getParsedCommand(),
273
                $deferredProcess->getCommand()->getLineNumber(),
274
                $deferredProcess->getCommand()->isIgnoreError(),
275
                $index,
276
                count($this->deferredProcesses)
277
            );
278
279
            foreach ($deferredProcess->getLog() as $logMessage) {
280
                $this->logger->log($logMessage);
281
            }
282
283
            if ($this->isProcessResultValid($deferredProcess->getProcess(), $deferredProcess->getCommand()->isIgnoreError())) {
284
                $this->logger->logSuccess();
285
            } else {
286
                $this->logger->logFailure();
287
            }
288
        }
289
290
        foreach ($this->deferredProcesses as $deferredProcess) {
291
            $this->testProcessResultValid($deferredProcess->getProcess(), $deferredProcess->getCommand()->isIgnoreError());
292
        }
293
294
        $this->deferredProcesses = [];
295
    }
296
297
    /**
298
     * @param string $parsedCommand
299
     * @param DeferredProcessCommand $command
300
     * @param Process $process
301
     */
302
    private function deferProcess(string $parsedCommand, DeferredProcessCommand $command, Process $process)
303
    {
304
        $deferredProcess = new DeferredProcess($parsedCommand, $command, $process);
305
306
        $process->start(function ($type, $response) use ($deferredProcess) {
307
            $deferredProcess->log(new LogMessage($response, $type === Process::ERR));
308
        });
309
310
        $this->deferredProcesses[] = $deferredProcess;
311
    }
312
313
    /**
314
     * @param Process $process
315
     * @param bool $ignoreError
316
     * @return bool
317
     */
318
    protected function isProcessResultValid(Process $process, bool $ignoreError): bool
319
    {
320
        return $ignoreError || $process->isSuccessful();
321
    }
322
}
323