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

Factory::make()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
dl 0
loc 26
rs 8.0555
c 1
b 0
f 0
cc 9
nc 9
nop 1
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\Context;
13
14
use Phplrt\Source\Exception\NotAccessibleException;
15
use Railt\SDL\Backend\Context;
16
use Railt\SDL\Backend\HashTableInterface;
17
use Railt\SDL\Backend\NameResolver\NameResolverInterface;
18
use Railt\SDL\Exception\TypeErrorException;
19
use Railt\SDL\Frontend\Ast\Definition\DirectiveDefinitionNode;
20
use Railt\SDL\Frontend\Ast\Definition\SchemaDefinitionNode;
21
use Railt\SDL\Frontend\Ast\Definition\Type\EnumTypeDefinitionNode;
22
use Railt\SDL\Frontend\Ast\Definition\Type\InputObjectTypeDefinitionNode;
23
use Railt\SDL\Frontend\Ast\Definition\Type\InterfaceTypeDefinitionNode;
24
use Railt\SDL\Frontend\Ast\Definition\Type\ObjectTypeDefinitionNode;
25
use Railt\SDL\Frontend\Ast\Definition\Type\ScalarTypeDefinitionNode;
26
use Railt\SDL\Frontend\Ast\Definition\Type\UnionTypeDefinitionNode;
27
use Railt\SDL\Frontend\Ast\DefinitionNode;
28
use Railt\SDL\Frontend\Ast\Node;
29
use Railt\TypeSystem\Reference\TypeReference;
30
use Railt\TypeSystem\Reference\TypeReferenceInterface;
31
32
/**
33
 * Class Factory
34
 */
35
class Factory
36
{
37
    /**
38
     * @var string
39
     */
40
    private const ERROR_NON_GENERIC_TYPE_USAGE = 'Type %s can not be used as generic type %1$s<%s>';
41
42
    /**
43
     * @var string
44
     */
45
    private const ERROR_GENERIC_ARGUMENTS = 'Generic type %s<%s> requires %d type arguments, but %d passed';
46
47
    /**
48
     * @var Context
49
     */
50
    private Context $ctx;
51
52
    /**
53
     * @var NameResolverInterface
54
     */
55
    private NameResolverInterface $resolver;
56
57
    /**
58
     * @var HashTableInterface
59
     */
60
    private HashTableInterface $vars;
61
62
    /**
63
     * Factory constructor.
64
     *
65
     * @param HashTableInterface $vars
66
     * @param NameResolverInterface $resolver
67
     * @param Context $context
68
     */
69
    public function __construct(HashTableInterface $vars, NameResolverInterface $resolver, Context $context)
70
    {
71
        $this->vars = $vars;
72
        $this->ctx = $context;
73
        $this->resolver = $resolver;
74
    }
75
76
    /**
77
     * @param Node $from
78
     * @param string $name
79
     * @param array|string[] $args
80
     * @return TypeReferenceInterface
81
     * @throws NotAccessibleException
82
     * @throws TypeErrorException
83
     * @throws \RuntimeException
84
     */
85
    public function ref(Node $from, string $name, array $args = []): TypeReferenceInterface
86
    {
87
        if ($args !== []) {
88
            $name = $this->compileGenericType($from, $name, $args);
89
        } else {
90
            $this->assertMissingTypeArguments($from, $name, $args);
91
        }
92
93
        return new TypeReference($this->ctx->getSchema(), $name);
94
    }
95
96
    /**
97
     * @param Node $from
98
     * @param string $name
99
     * @param array $args
100
     * @return void
101
     * @throws NotAccessibleException
102
     * @throws TypeErrorException
103
     * @throws \RuntimeException
104
     */
105
    private function assertMissingTypeArguments(Node $from, string $name, array $args = []): void
106
    {
107
        $context = $this->ctx->fetch($name);
108
109
        if ($context === null) {
110
            return;
111
        }
112
113
        $needle = $context->getGenericArguments();
114
115
        if ($needle !== []) {
116
            $message = \vsprintf(self::ERROR_GENERIC_ARGUMENTS, [
117
                $name,
118
                \implode(', ', $needle),
119
                \count($needle),
120
                \count($args),
121
            ]);
122
123
            throw new TypeErrorException($message, $from);
124
        }
125
    }
126
127
    /**
128
     * @param Node $from
129
     * @param string $name
130
     * @param array|string[] $args
131
     * @return string
132
     * @throws NotAccessibleException
133
     * @throws TypeErrorException
134
     * @throws \RuntimeException
135
     */
136
    private function compileGenericType(Node $from, string $name, array $args): string
137
    {
138
        $generic = $this->resolver->resolve($name, $args);
139
140
        if (! $this->ctx->hasType($generic)) {
141
            $context = $this->getContext($from, $name, $args);
142
143
            $schema = $this->ctx->getSchema();
144
145
            $this->assertGenericArguments($from, $name, $context->getGenericArguments(), $args);
146
147
            $schema->addType($context->resolve($args)->withName($generic));
0 ignored issues
show
Bug introduced by
The method withName() does not exist on GraphQL\Contracts\TypeSystem\DefinitionInterface. It seems like you code against a sub-type of GraphQL\Contracts\TypeSystem\DefinitionInterface such as Railt\TypeSystem\Field or Railt\TypeSystem\Type\ObjectType or Railt\TypeSystem\Type\ScalarType or Railt\TypeSystem\Type\InterfaceType or Railt\TypeSystem\Type\UnionType or Railt\TypeSystem\Type\EnumType or Railt\TypeSystem\Type\ObjectType or Railt\TypeSystem\Type\ScalarType or Railt\TypeSystem\Type\InputObjectType or Railt\TypeSystem\Type\InterfaceType or Railt\TypeSystem\Type\UnionType or Railt\TypeSystem\Type\EnumType or Railt\TypeSystem\Type\ObjectType or Railt\TypeSystem\Type\InterfaceType or Railt\TypeSystem\Type\UnionType or Railt\TypeSystem\Type\NamedType or Railt\TypeSystem\Type\ObjectType or Railt\TypeSystem\Type\ScalarType or Railt\TypeSystem\Type\InterfaceType or Railt\TypeSystem\Type\UnionType or Railt\TypeSystem\Type\InputObjectType or Railt\TypeSystem\Type\InterfaceType or Railt\TypeSystem\Type\UnionType or Railt\TypeSystem\Type\EnumType or Railt\TypeSystem\Type\ScalarType or Railt\TypeSystem\Type\EnumType or Railt\TypeSystem\Type\NamedType or Railt\TypeSystem\Type\ScalarType or Railt\TypeSystem\Type\InputObjectType or Railt\TypeSystem\Type\EnumType or Railt\TypeSystem\AbstractInputField or Railt\TypeSystem\Field or Railt\TypeSystem\Directive or Railt\TypeSystem\EnumValue or Railt\TypeSystem\Type\NamedType or Railt\TypeSystem\Argument or Railt\TypeSystem\EnumValue or Railt\TypeSystem\InputField or Railt\TypeSystem\Directive. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

147
            $schema->addType($context->resolve($args)->/** @scrutinizer ignore-call */ withName($generic));
Loading history...
148
        }
149
150
        return $generic;
151
    }
152
153
    /**
154
     * @param Node $from
155
     * @param string $name
156
     * @param array|string[] $args
157
     * @return NamedDefinitionContextInterface
158
     * @throws NotAccessibleException
159
     * @throws TypeErrorException
160
     * @throws \RuntimeException
161
     */
162
    private function getContext(Node $from, string $name, array $args): NamedDefinitionContextInterface
163
    {
164
        $context = $this->ctx->fetch($name);
165
166
        if ($context === null) {
167
            $message = self::ERROR_NON_GENERIC_TYPE_USAGE;
168
            $message = \sprintf($message, $name, \implode(',', $args));
169
170
            throw new TypeErrorException($message, $from);
171
        }
172
173
        return $context;
174
    }
175
176
    /**
177
     * @param Node $from
178
     * @param string $name
179
     * @param array $definition
180
     * @param array $passed
181
     * @return void
182
     * @throws NotAccessibleException
183
     * @throws TypeErrorException
184
     * @throws \RuntimeException
185
     */
186
    private function assertGenericArguments(Node $from, string $name, array $definition, array $passed): void
187
    {
188
        [$definitions, $arguments] = [\count($definition), \count($passed)];
189
190
        if ($definitions !== $arguments) {
191
            $message = \vsprintf(self::ERROR_GENERIC_ARGUMENTS, [
192
                $name,
193
                \implode(', ', $definition),
194
                \count($definition),
195
                \count($passed),
196
            ]);
197
198
            throw new TypeErrorException($message, $from);
199
        }
200
    }
201
202
    /**
203
     * @param DefinitionNode $node
204
     * @return DefinitionContextInterface|NamedDefinitionContextInterface
205
     * @throws \LogicException
206
     */
207
    public function make(DefinitionNode $node): DefinitionContextInterface
208
    {
209
        switch (true) {
210
            case $node instanceof ObjectTypeDefinitionNode:
211
                return new ObjectTypeDefinitionContext($this->vars, $this, $node);
212
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
213
214
            case $node instanceof InterfaceTypeDefinitionNode:
215
                return new InterfaceTypeDefinitionContext($this->vars, $this, $node);
216
217
            case $node instanceof ScalarTypeDefinitionNode:
218
                return new ScalarTypeDefinitionContext($this->vars, $this, $node);
219
220
            case $node instanceof EnumTypeDefinitionNode:
221
            case $node instanceof InputObjectTypeDefinitionNode:
222
            case $node instanceof UnionTypeDefinitionNode:
223
            case $node instanceof SchemaDefinitionNode:
224
                throw new \LogicException($node->name->value . ' context not implemented yet');
225
226
                break;
227
228
            case $node instanceof DirectiveDefinitionNode:
229
                return new DirectiveDefinitionContext($this->vars, $this, $node);
230
        }
231
232
        throw new \LogicException(\get_class($node) . ' is an unresolvable');
233
    }
234
}
235