Test Setup Failed
Push — master ( 1595cb...050b68 )
by Kirill
21:00
created

LinkerVisitor   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 13
eloc 30
dl 0
loc 124
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Railt package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Railt\SDL\Backend\Linker;
13
14
use GraphQL\Contracts\TypeSystem\Type\NamedTypeInterface;
15
use Phplrt\Contracts\Ast\NodeInterface;
16
use Phplrt\Source\Exception\NotAccessibleException;
17
use Phplrt\Visitor\Visitor;
18
use Railt\SDL\Backend\Context;
19
use Railt\SDL\Exception\TypeNotFoundException;
20
use Railt\SDL\Frontend\Ast\Definition\Type\TypeDefinitionNode;
21
use Railt\SDL\Frontend\Ast\Executable\DirectiveNode;
22
use Railt\SDL\Frontend\Ast\Identifier;
23
use Railt\SDL\Frontend\Ast\Type\NamedDirectiveNode;
24
use Railt\SDL\Frontend\Ast\Type\NamedTypeNode;
25
use Railt\SDL\Frontend\Ast\TypeName;
26
27
/**
28
 * Class LinkerVisitor
29
 */
30
class LinkerVisitor extends Visitor
31
{
32
    /**
33
     * @var string
34
     */
35
    private const ERROR_TYPE_NOT_FOUND = 'Type "%s" not found or could not be loaded';
36
37
    /**
38
     * @var string
39
     */
40
    private const ERROR_DIRECTIVE_NOT_FOUND = 'Directive "@%s" not found or could not be loaded';
41
42
    /**
43
     * @var Context
44
     */
45
    private Context $context;
46
47
    /**
48
     * @var LinkerInterface
49
     */
50
    private LinkerInterface $linker;
51
52
    /**
53
     * @var array|string[]
54
     */
55
    private array $genericArguments = [];
56
57
    /**
58
     * LinkerVisitor constructor.
59
     *
60
     * @param Context $context
61
     * @param LinkerInterface $linker
62
     */
63
    public function __construct(Context $context, LinkerInterface $linker)
64
    {
65
        $this->context = $context;
66
        $this->linker = $linker;
67
    }
68
69
    /**
70
     * @param NodeInterface $node
71
     * @return void
72
     */
73
    public function enter(NodeInterface $node): void
74
    {
75
        if ($node instanceof TypeDefinitionNode && $node->name instanceof TypeName) {
76
            $this->loadGenericArguments($node->name);
77
        }
78
    }
79
80
    /**
81
     * @param TypeName $type
82
     * @return void
83
     */
84
    private function loadGenericArguments(TypeName $type): void
85
    {
86
        $map = fn(Identifier $name): string => $name->value;
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ':', expecting T_DOUBLE_ARROW on line 86 at column 35
Loading history...
87
88
        $this->genericArguments = \array_map($map, $type->arguments);
89
    }
90
91
    /**
92
     * @param NodeInterface $node
93
     * @return mixed|void|null
94
     */
95
    public function leave(NodeInterface $node)
96
    {
97
        switch (true) {
98
            //
99
            // If current context was closed then we should remove
100
            // current state of generic arguments.
101
            //
102
            case $node instanceof TypeDefinitionNode:
103
                $this->genericArguments = [];
104
105
                break;
106
107
            //
108
            // Load all type dependencies.
109
            //
110
            // Note: Skip type loading if type is part of type template parameter.
111
            //
112
            case $node instanceof NamedTypeNode:
113
                if (! \in_array($node->name->value, $this->genericArguments, true)) {
114
                    $this->assertTypeExists($node);
115
                }
116
117
                break;
118
119
            //
120
            // Load all directive dependencies
121
            //
122
            case $node instanceof DirectiveNode:
123
                $this->assertDirectiveExists($node->name);
124
                break;
125
        }
126
    }
127
128
    /**
129
     * @param NamedTypeNode $name
130
     * @return void
131
     */
132
    private function assertTypeExists(NamedTypeNode $name): void
133
    {
134
        if (! $this->context->hasType($name->name->value)) {
135
            $this->loadOr($name->name->value, function () use ($name): void {
136
                throw $this->typeNotFound($name);
137
            });
138
        }
139
    }
140
141
    /**
142
     * @param string $type
143
     * @param \Closure $otherwise
144
     * @return void
145
     */
146
    private function loadOr(string $type, \Closure $otherwise): void
147
    {
148
        $schema = $this->context->getSchema();
149
150
        foreach ($this->linker->getAutoloaders() as $autoloader) {
151
            $result = $autoloader($type);
152
153
            switch (true) {
154
                case $result instanceof NamedTypeInterface:
155
                    $schema->addType($result);
156
157
                    return;
158
            }
159
        }
160
161
        $otherwise();
162
    }
163
164
    /**
165
     * @param NamedTypeNode $name
166
     * @return TypeNotFoundException
167
     * @throws NotAccessibleException
168
     * @throws \RuntimeException
169
     */
170
    private function typeNotFound(NamedTypeNode $name): TypeNotFoundException
171
    {
172
        $error = \sprintf(self::ERROR_TYPE_NOT_FOUND, $name->name->value);
173
174
        return new TypeNotFoundException($error, $name);
175
    }
176
177
    /**
178
     * @param NamedDirectiveNode $name
179
     * @return void
180
     */
181
    private function assertDirectiveExists(NamedDirectiveNode $name): void
182
    {
183
        $context = $this->context->getDirective($name->name->value);
184
185
        if ($context === null) {
186
            $this->loadOr('@' . $name->name->value, function () use ($name): void {
187
                throw $this->directiveNotFound($name);
188
            });
189
        }
190
    }
191
192
    /**
193
     * @param NamedDirectiveNode $name
194
     * @return TypeNotFoundException
195
     * @throws NotAccessibleException
196
     * @throws \RuntimeException
197
     */
198
    private function directiveNotFound(NamedDirectiveNode $name): TypeNotFoundException
199
    {
200
        $error = \sprintf(self::ERROR_DIRECTIVE_NOT_FOUND, $name->name->value);
201
202
        return new TypeNotFoundException($error, $name);
203
    }
204
}
205