Completed
Push — master ( b2a65d...63a169 )
by Kirill
02:18
created

Storage::invoke()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 12
rs 9.8666
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\Frontend\Definition;
11
12
use Railt\Reflection\Contracts\Definition\TypeDefinition;
13
use Railt\SDL\Exception\NotFoundException;
14
use Railt\SDL\Exception\TypeConflictException;
15
use Railt\SDL\Frontend\Context\ContextInterface;
16
use Railt\SDL\Frontend\Deferred\Deferred;
17
use Railt\SDL\Frontend\Deferred\DeferredInterface;
18
use Railt\SDL\Frontend\Map;
19
use Railt\SDL\IR\Definition\DefinitionValueObject;
20
use Railt\SDL\IR\Type\TypeNameInterface;
21
use Railt\SDL\Naming\StrategyInterface;
22
23
/**
24
 * Class Storage
25
 */
26
class Storage
27
{
28
    /**
29
     * @var array|DefinitionInterface[]
30
     */
31
    private $definitions;
32
33
    /**
34
     * @var array|DeferredInterface[]
35
     */
36
    private $resolvers;
37
38
    /**
39
     * @var DefinitionValueObject[]
40
     */
41
    private $resolved = [];
42
43
    /**
44
     * @var StrategyInterface
45
     */
46
    private $naming;
47
48
    /**
49
     * Pipeline constructor.
50
     * @param StrategyInterface $naming
51
     */
52
    public function __construct(StrategyInterface $naming)
53
    {
54
        $this->naming = $naming;
55
56
        $this->definitions = new Map(function (TypeNameInterface $name): string {
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Railt\SDL\Frontend\...ullyQualifiedName(); }) of type object<Railt\SDL\Frontend\Map> is incompatible with the declared type array of property $definitions.

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...
57
            return $name->getFullyQualifiedName();
58
        });
59
60
        $this->resolvers = new Map(function (DefinitionInterface $definition): string {
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Railt\SDL\Frontend\...ullyQualifiedName(); }) of type object<Railt\SDL\Frontend\Map> is incompatible with the declared type array of property $resolvers.

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...
61
            return $definition->getName()->getFullyQualifiedName();
62
        });
63
    }
64
65
    /**
66
     * @param DefinitionInterface $definition
67
     * @param DeferredInterface $deferred
68
     * @return $this|Storage
69
     */
70
    public function remember(DefinitionInterface $definition, DeferredInterface $deferred): Storage
71
    {
72
        $this->resolvers[$definition] = $deferred;
73
        $this->definitions[$definition->getName()] = $definition;
74
75
        return $this;
76
    }
77
78
    /**
79
     * @param InvocationInterface $invocation
80
     * @return \Generator|mixed
81
     */
82
    public function invoke(InvocationInterface $invocation)
83
    {
84
        $name = $this->naming->resolve($invocation->getName(), $invocation->getArguments());
0 ignored issues
show
Bug introduced by
It seems like $invocation->getArguments() targeting Railt\SDL\Frontend\Defin...terface::getArguments() can also be of type array<integer,object<Rai...n\InvocationInterface>>; however, Railt\SDL\Naming\StrategyInterface::resolve() does only seem to accept object<Railt\SDL\Naming\...lTable\ValueInterface>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
85
86
        if ($this->isResolved($name)) {
87
            return $this->resolved[$name];
88
        }
89
90
        [$definition, $deferred] = $this->get($invocation->getName(), $invocation->getContext());
0 ignored issues
show
Bug introduced by
The variable $definition does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $deferred does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
91
92
        return $deferred->invoke($definition, $invocation);
93
    }
94
95
    /**
96
     * @param string $name
97
     * @return bool
98
     */
99
    private function isResolved(string $name): bool
100
    {
101
        return isset($this->resolved[$name]);
102
    }
103
104
    /**
105
     * @param TypeNameInterface $name
106
     * @param ContextInterface $ctx
107
     * @return array
108
     */
109
    private function get(TypeNameInterface $name, ContextInterface $ctx): array
110
    {
111
        $result = $this->find($name) ?? $this->find($name->in($ctx->getName()));
112
113
        if ($result === null) {
114
            $error = 'Type %s not found or could not be loaded';
115
            throw new NotFoundException(\sprintf($error, $name));
116
        }
117
118
        return $result;
119
    }
120
121
    /**
122
     * @param TypeNameInterface $name
123
     * @return array|null
124
     */
125
    private function find(TypeNameInterface $name): ?array
126
    {
127
        if (isset($this->definitions[$name])) {
128
            $definition = $this->definitions[$name];
129
130
            return [$definition, $this->resolvers[$definition]];
131
        }
132
133
        return null;
134
    }
135
136
    /**
137
     * @param InvocationInterface $name
138
     * @return bool
139
     */
140
    public function resolved(InvocationInterface $name): bool
141
    {
142
        return $this->isResolved($this->naming->resolve($name->getName(), $name->getArguments()));
0 ignored issues
show
Bug introduced by
It seems like $name->getArguments() targeting Railt\SDL\Frontend\Defin...terface::getArguments() can also be of type array<integer,object<Rai...n\InvocationInterface>>; however, Railt\SDL\Naming\StrategyInterface::resolve() does only seem to accept object<Railt\SDL\Naming\...lTable\ValueInterface>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
143
    }
144
145
    /**
146
     * @param \Closure $filter
147
     * @return \Generator|DeferredInterface[]
148
     */
149
    public function export(\Closure $filter): \Generator
150
    {
151
        /**
152
         * @var DefinitionInterface $definition
153
         * @var DeferredInterface $then
154
         */
155
        foreach ($this->resolvers as $definition => $then) {
156
            if ($filter($definition)) {
157
                yield $this->toInvocationDeferred($definition, $then);
0 ignored issues
show
Documentation introduced by
$definition is of type integer|string, but the function expects a object<Railt\SDL\Fronten...on\DefinitionInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
158
            }
159
        }
160
    }
161
162
    /**
163
     * @param DefinitionInterface $def
164
     * @param DeferredInterface $then
165
     * @return DeferredInterface
166
     */
167
    private function toInvocationDeferred(DefinitionInterface $def, DeferredInterface $then): DeferredInterface
168
    {
169
        return new Deferred($this->toInvocationDeferredCallback($def, $then));
170
    }
171
172
    /**
173
     * @param DefinitionInterface $original
174
     * @param DeferredInterface $then
175
     * @return \Closure
176
     */
177
    private function toInvocationDeferredCallback(DefinitionInterface $original, DeferredInterface $then): \Closure
178
    {
179
        return function (DefinitionInterface $definition = null, InvocationInterface $invocation = null) use (
180
            $original,
181
            $then
182
        ) {
183
            $definition = $definition ?? $original;
184
            $invocation = $invocation ?? new Invocation($definition->getName(), $definition->getContext());
185
186
            return $then->invoke($definition, $invocation);
187
        };
188
    }
189
}
190