Completed
Push — master ( ee9170...1dcd4f )
by Kirill
03:45
created

DefinitionDelegate::before()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
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\Compiler\Definition;
11
12
use Railt\Parser\Ast\Delegate;
13
use Railt\Parser\Ast\NodeInterface;
14
use Railt\Parser\Ast\Rule;
15
use Railt\Parser\Ast\RuleInterface;
16
use Railt\Parser\Environment;
17
use Railt\Reflection\AbstractTypeDefinition;
18
use Railt\Reflection\Contracts\Definition;
19
use Railt\Reflection\Contracts\Definition\TypeDefinition;
20
use Railt\Reflection\Contracts\Document;
21
use Railt\SDL\CallStack;
22
use Railt\SDL\Compiler\Pipeline;
23
use Railt\SDL\Compiler\Value;
24
use Railt\SDL\Compiler\Value\ValueInterface;
25
use Railt\SDL\Exception\CompilerException;
26
27
/**
28
 * Class DefinitionDelegate
29
 */
30
abstract class DefinitionDelegate extends Rule implements Delegate
31
{
32
    /**
33
     * @var TypeDefinition|AbstractTypeDefinition
34
     */
35
    protected $definition;
36
37
    /**
38
     * @var CallStack
39
     */
40
    private $stack;
41
42
    /**
43
     * @var Document|\Railt\Reflection\Document
44
     */
45
    private $document;
46
47
    /**
48
     * @var Pipeline
49
     */
50
    private $pipeline;
51
52
    /**
53
     * @param Environment $env
54
     */
55
    public function boot(Environment $env): void
56
    {
57
        /** @var \Railt\Reflection\Document $document */
58
        $this->document = $env->get(Document::class);
59
        $this->stack    = $env->get(CallStack::class);
60
        $this->pipeline = $env->get(Pipeline::class);
61
62
        $this->definition = $this->create($this->document);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->create($this->document) of type object<Railt\Reflection\Contracts\Definition> is incompatible with the declared type object<Railt\Reflection\...AbstractTypeDefinition> of property $definition.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
        $this->definition->withOffset($this->getOffset());
64
    }
65
66
    /**
67
     * @param Document $document
68
     * @return Definition
69
     */
70
    abstract protected function create(Document $document): Definition;
71
72
    /**
73
     * @param int $priority
74
     * @param callable $then
75
     */
76
    protected function future(int $priority, callable $then): void
77
    {
78
        $this->pipeline->push($priority, function () use ($then) {
79
            $this->transaction($this->definition, function () use ($then) {
80
                $then();
81
            });
82
        });
83
    }
84
85
    /**
86
     * @param Definition $definition
87
     * @param \Closure $then
88
     * @return mixed
89
     */
90
    protected function transaction(Definition $definition, \Closure $then)
91
    {
92
        return $this->stack->transaction($definition, $then);
93
    }
94
95
    /**
96
     * @return CallStack
97
     */
98
    protected function getCallStack(): CallStack
99
    {
100
        return $this->stack;
101
    }
102
103
    /**
104
     * @param NodeInterface|null $node
105
     * @return null|string
106
     */
107
    protected function getTypeName(NodeInterface $node = null): ?string
108
    {
109
        /** @var RuleInterface|null $name */
110
        $name = ($node ?? $this)->first('TypeName', 1);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\Parser\Ast\NodeInterface as the method first() does only exist in the following implementations of said interface: Railt\Compiler\Grammar\Delegate\IncludeDelegate, Railt\Compiler\Grammar\Delegate\RuleDelegate, Railt\Compiler\Grammar\Delegate\TokenDelegate, Railt\Parser\Ast\Rule, Railt\SDL\Compiler\Definition\DefinitionDelegate, Railt\SDL\Compiler\Definition\DirectiveDelegate, Railt\SDL\Compiler\Definition\EnumDelegate, Railt\SDL\Compiler\Definition\InputUnionDelegate, Railt\SDL\Compiler\Definition\InterfaceDelegate, Railt\SDL\Compiler\Definition\ObjectDelegate, Railt\SDL\Compiler\Definition\ScalarDelegate, Railt\SDL\Compiler\Definition\SchemaDelegate, Railt\SDL\Compiler\Defin...\TypeDefinitionDelegate, Railt\SDL\Compiler\Definition\UnionDelegate.

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...
111
112
        return $name ? $name->getChild(0)->getValue() : null;
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on Railt\Parser\Ast\NodeInterface. Did you maybe mean getValues()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
113
    }
114
115
    /**
116
     * @param NodeInterface $rule
117
     * @return ValueInterface
118
     * @throws \Railt\Io\Exception\ExternalFileException
119
     */
120
    protected function value(NodeInterface $rule): ValueInterface
121
    {
122
        try {
123
            return Value::parse($rule, $this->definition->getFile());
124
        } catch (CompilerException $e) {
125
            throw $this->error($e)->throwsIn($this->definition->getFile(), $rule->getOffset());
126
        }
127
    }
128
129
    /**
130
     * @param CompilerException $exception
131
     * @return CompilerException
132
     */
133
    protected function error(CompilerException $exception): CompilerException
134
    {
135
        return $exception->in($this->definition)->using($this->stack);
136
    }
137
138
    /**
139
     * @param Definition $def
140
     */
141
    protected function push(Definition $def): void
142
    {
143
        $this->stack->push($def);
144
    }
145
146
    /**
147
     * @param int $count
148
     * @return void
149
     */
150
    protected function pop(int $count = 1): void
151
    {
152
        $count = \max(1, $count);
153
154
        for ($i = 0; $i <= $count; ++$i) {
155
            $this->stack->pop();
156
        }
157
    }
158
}
159