Test Failed
Push — master ( ecd78b...d05c81 )
by Kirill
02:43
created

Runtime::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\SDL\Backend;
11
12
use Railt\Io\Readable;
13
use Railt\Reflection\Contracts\Reflection;
14
use Railt\SDL\Backend\State\OpenState;
15
use Railt\SDL\Backend\State\StateInterface;
16
use Railt\SDL\Exception\VmException;
17
use Railt\SDL\Frontend\IR\JoinedOpcode;
18
use Railt\SDL\Frontend\IR\OpcodeInterface;
19
20
/**
21
 * Class Runtime
22
 */
23
class Runtime
24
{
25
    /**
26
     * @var string[]|StateInterface[]
27
     */
28
    private const RESOLVERS = [
29
        OpcodeInterface::RL_OPEN => OpenState::class,
30
    ];
31
32
    /**
33
     * @var array
34
     */
35
    private $result = [];
36
37
    /**
38
     * @var Readable
39
     */
40
    private $input;
41
42
    /**
43
     * @var Reflection
44
     */
45
    private $root;
46
47
    /**
48
     * Runtime constructor.
49
     * @param Reflection $root
50
     * @param Readable $input
51
     */
52
    public function __construct(Reflection $root, Readable $input)
53
    {
54
        $this->input = $input;
55
        $this->root = $root;
56
    }
57
58
    /**
59
     * @return Readable
60
     */
61
    public function getInput(): Readable
62
    {
63
        return $this->input;
64
    }
65
66
    /**
67
     * @param int $id
68
     * @return mixed|null
69
     */
70
    public function get(int $id)
71
    {
72
        return $this->result[$id] ?? null;
73
    }
74
75
    /**
76
     * @return Reflection
77
     */
78
    public function getReflection(): Reflection
79
    {
80
        return $this->root;
81
    }
82
83
    /**
84
     * @param iterable|OpcodeInterface[]|JoinedOpcode[] $opcodes
85
     * @return iterable
86
     * @throws \Railt\Io\Exception\ExternalFileException
87
     */
88
    public function execute(iterable $opcodes): iterable
89
    {
90
        foreach ($opcodes as $opcode) {
91
            yield $opcode => $this->result[$opcode->getId()] = $this->getState($opcode)->resolve($opcode);
92
        }
93
    }
94
95
    /**
96
     * @param OpcodeInterface|JoinedOpcode $opcode
97
     * @return StateInterface
98
     * @throws \Railt\Io\Exception\ExternalFileException
99
     */
100
    private function getState(OpcodeInterface $opcode): StateInterface
101
    {
102
        $state = self::RESOLVERS[$opcode->getOperation()] ?? null;
103
104
        if ($state === null) {
105
            $error = 'Unrecognized opcode (#%d) %s';
106
            throw new VmException($opcode, $error, $opcode->getId(), $opcode->getName());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\SDL\Frontend\IR\OpcodeInterface as the method getId() does only exist in the following implementations of said interface: Railt\SDL\Frontend\IR\JoinedOpcode.

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...
107
        }
108
109
        return new $state($this);
110
    }
111
}
112