Passed
Push — master ( 81686d...1595cb )
by Kirill
04:41
created

LinkerVisitor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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\Backend\Context\CompiledTypeContext;
20
use Railt\SDL\Exception\TypeNotFoundException;
21
use Railt\SDL\Frontend\Ast\Executable\DirectiveNode;
22
use Railt\SDL\Frontend\Ast\Type\NamedDirectiveNode;
23
use Railt\SDL\Frontend\Ast\Type\NamedTypeNode;
24
25
/**
26
 * Class LinkerVisitor
27
 */
28
class LinkerVisitor extends Visitor
29
{
30
    /**
31
     * @var string
32
     */
33
    private const ERROR_TYPE_NOT_FOUND = 'Type "%s" not found or could not be loaded';
34
35
    /**
36
     * @var string
37
     */
38
    private const ERROR_DIRECTIVE_NOT_FOUND = 'Directive "@%s" not found or could not be loaded';
39
40
    /**
41
     * @var Context
42
     */
43
    private Context $context;
44
45
    /**
46
     * @var LinkerInterface
47
     */
48
    private LinkerInterface $linker;
49
50
    /**
51
     * LinkerVisitor constructor.
52
     *
53
     * @param Context $context
54
     * @param LinkerInterface $linker
55
     */
56
    public function __construct(Context $context, LinkerInterface $linker)
57
    {
58
        $this->context = $context;
59
        $this->linker = $linker;
60
    }
61
62
    /**
63
     * @param NodeInterface $node
64
     * @return mixed|void|null
65
     */
66
    public function leave(NodeInterface $node)
67
    {
68
        if ($node instanceof NamedTypeNode) {
69
            $this->assertTypeExists($node);
70
        }
71
72
        if ($node instanceof DirectiveNode) {
73
            $this->assertDirectiveExists($node->name);
74
        }
75
    }
76
77
    /**
78
     * @param NamedTypeNode $name
79
     * @return void
80
     */
81
    private function assertTypeExists(NamedTypeNode $name): void
82
    {
83
        $context = $this->context->getType($name->name->value);
84
85
        if ($context === null) {
86
            $this->loadOr($name->name->value, function () use ($name): void {
87
                throw $this->typeNotFound($name);
88
            });
89
        }
90
    }
91
92
    /**
93
     * @param string $type
94
     * @param \Closure $otherwise
95
     * @return void
96
     */
97
    private function loadOr(string $type, \Closure $otherwise): void
98
    {
99
        foreach ($this->linker->getAutoloaders() as $autoloader) {
100
            $result = $autoloader($type);
101
102
            switch (true) {
103
                case $result instanceof NamedTypeInterface:
104
                    $this->context->addType(new CompiledTypeContext($result));
105
106
                    return;
107
            }
108
        }
109
110
        $otherwise();
111
    }
112
113
    /**
114
     * @param NamedTypeNode $name
115
     * @return TypeNotFoundException
116
     * @throws NotAccessibleException
117
     * @throws \RuntimeException
118
     */
119
    private function typeNotFound(NamedTypeNode $name): TypeNotFoundException
120
    {
121
        $error = \sprintf(self::ERROR_TYPE_NOT_FOUND, $name->name->value);
122
123
        return new TypeNotFoundException($error, $name);
124
    }
125
126
    /**
127
     * @param NamedDirectiveNode $name
128
     * @return void
129
     */
130
    private function assertDirectiveExists(NamedDirectiveNode $name): void
131
    {
132
        $context = $this->context->getDirective($name->name->value);
133
134
        if ($context === null) {
135
            $this->loadOr('@' . $name->name->value, function () use ($name): void {
136
                throw $this->directiveNotFound($name);
137
            });
138
        }
139
    }
140
141
    /**
142
     * @param NamedDirectiveNode $name
143
     * @return TypeNotFoundException
144
     * @throws NotAccessibleException
145
     * @throws \RuntimeException
146
     */
147
    private function directiveNotFound(NamedDirectiveNode $name): TypeNotFoundException
148
    {
149
        $error = \sprintf(self::ERROR_DIRECTIVE_NOT_FOUND, $name->name->value);
150
151
        return new TypeNotFoundException($error, $name);
152
    }
153
}
154