Passed
Pull Request — master (#19)
by Christoffer
02:06
created

DefinitionBuilder::buildInterfaceType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Language\AST\Schema;
4
5
use Digia\GraphQL\Language\AST\Node\DirectiveDefinitionNode;
6
use Digia\GraphQL\Language\AST\Node\EnumTypeDefinitionNode;
7
use Digia\GraphQL\Language\AST\Node\EnumValueDefinitionNode;
8
use Digia\GraphQL\Language\AST\Node\FieldDefinitionNode;
9
use Digia\GraphQL\Language\AST\Node\InputObjectTypeDefinitionNode;
10
use Digia\GraphQL\Language\AST\Node\InputValueDefinitionNode;
11
use Digia\GraphQL\Language\AST\Node\InterfaceTypeDefinitionNode;
12
use Digia\GraphQL\Language\AST\Node\ListTypeNode;
13
use Digia\GraphQL\Language\AST\Node\NamedTypeNode;
14
use Digia\GraphQL\Language\AST\Node\NameNode;
15
use Digia\GraphQL\Language\AST\Node\NodeInterface;
16
use Digia\GraphQL\Language\AST\Node\NonNullTypeNode;
17
use Digia\GraphQL\Language\AST\Node\ObjectTypeDefinitionNode;
18
use Digia\GraphQL\Language\AST\Node\ScalarTypeDefinitionNode;
19
use Digia\GraphQL\Language\AST\Node\TypeDefinitionNodeInterface;
20
use Digia\GraphQL\Language\AST\Node\TypeNodeInterface;
21
use Digia\GraphQL\Language\AST\Node\UnionTypeDefinitionNode;
22
use Digia\GraphQL\Type\Definition\DirectiveInterface;
23
use Digia\GraphQL\Type\Definition\EnumType;
24
use Digia\GraphQL\Type\Definition\InputObjectType;
25
use Digia\GraphQL\Type\Definition\InterfaceType;
26
use Digia\GraphQL\Type\Definition\NamedTypeInterface;
27
use Digia\GraphQL\Type\Definition\ObjectType;
28
use Digia\GraphQL\Type\Definition\ScalarType;
29
use Digia\GraphQL\Type\Definition\TypeInterface;
30
use Digia\GraphQL\Type\Definition\UnionType;
31
use function Digia\GraphQL\Execution\getDirectiveValues;
32
use function Digia\GraphQL\Language\valueFromAST;
33
use function Digia\GraphQL\Type\assertNullableType;
34
use function Digia\GraphQL\Type\GraphQLDirective;
35
use function Digia\GraphQL\Type\GraphQLEnumType;
36
use function Digia\GraphQL\Type\GraphQLInputObjectType;
37
use function Digia\GraphQL\Type\GraphQLInterfaceType;
38
use function Digia\GraphQL\Type\GraphQLList;
39
use function Digia\GraphQL\Type\GraphQLNonNull;
40
use function Digia\GraphQL\Type\GraphQLObjectType;
41
use function Digia\GraphQL\Type\GraphQLScalarType;
42
use function Digia\GraphQL\Type\GraphQLUnionType;
43
use function Digia\GraphQL\Type\specifiedScalarTypes;
44
use function Digia\GraphQL\Util\keyMap;
45
use function Digia\GraphQL\Util\keyValMap;
46
use Psr\SimpleCache\CacheInterface;
47
48
class DefinitionBuilder implements DefinitionBuilderInterface
49
{
50
51
    /**
52
     * @var ?callable
53
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?callable at position 0 could not be parsed: Unknown type name '?callable' at position 0 in ?callable.
Loading history...
54
    protected $resolveTypeFunction;
55
56
    /**
57
     * @var CacheInterface
58
     */
59
    protected $cache;
60
61
    /**
62
     * @var array
63
     */
64
    protected $typeDefinitionsMap;
65
66
    /**
67
     * DefinitionBuilder constructor.
68
     *
69
     * @param callable $resolveTypeFunction
70
     * @throws \Psr\SimpleCache\InvalidArgumentException
71
     */
72
    public function __construct(callable $resolveTypeFunction, CacheInterface $cache)
73
    {
74
        $this->typeDefinitionsMap  = [];
75
        $this->resolveTypeFunction = $resolveTypeFunction;
76
77
        $builtInTypes = keyMap(
78
            array_merge(specifiedScalarTypes()/*, introspectionTypes()*/),
79
            function (NamedTypeInterface $type) {
80
                return $type->getName();
81
            }
82
        );
83
84
        foreach ($builtInTypes as $name => $type) {
85
            $cache->set($name, $type);
86
        }
87
88
        $this->cache = $cache;
89
    }
90
91
    /**
92
     * @inheritdoc
93
     */
94
    public function setTypeDefinitionMap(array $typeDefinitionMap)
95
    {
96
        $this->typeDefinitionsMap = $typeDefinitionMap;
97
        return $this;
98
    }
99
100
    /**
101
     * @param NamedTypeNode|TypeDefinitionNodeInterface $node
102
     * @inheritdoc
103
     */
104
    public function buildType(NodeInterface $node): TypeInterface
105
    {
106
        $typeName = $node->getNameValue();
0 ignored issues
show
Bug introduced by
The method getNameValue() does not exist on Digia\GraphQL\Language\AST\Node\NodeInterface. It seems like you code against a sub-type of Digia\GraphQL\Language\AST\Node\NodeInterface such as Digia\GraphQL\Language\AST\Node\DirectiveNode or Digia\GraphQL\Language\AST\Node\ArgumentNode or Digia\GraphQL\Language\AST\Node\ObjectFieldNode or Digia\GraphQL\Language\AST\Node\FragmentSpreadNode or Digia\GraphQL\Language\A...\EnumTypeDefinitionNode or Digia\GraphQL\Language\A...EnumValueDefinitionNode or Digia\GraphQL\Language\A...DirectiveDefinitionNode or Digia\GraphQL\Language\A...nputValueDefinitionNode or Digia\GraphQL\Language\A...ode\FieldDefinitionNode or Digia\GraphQL\Language\A...\VariableDefinitionNode or Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...OperationDefinitionNode or Digia\GraphQL\Language\A...\FragmentDefinitionNode or Digia\GraphQL\Language\A...erfaceTypeExtensionNode or Digia\GraphQL\Language\A...ScalarTypeExtensionNode or Digia\GraphQL\Language\A...\UnionTypeExtensionNode or Digia\GraphQL\Language\A...ObjectTypeExtensionNode or Digia\GraphQL\Language\A...e\EnumTypeExtensionNode or Digia\GraphQL\Language\A...ObjectTypeExtensionNode or Digia\GraphQL\Language\AST\Node\VariableNode or Digia\GraphQL\Language\AST\Node\FieldNode or Digia\GraphQL\Language\AST\Node\NamedTypeNode. ( Ignorable by Annotation )

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

106
        /** @scrutinizer ignore-call */ 
107
        $typeName = $node->getNameValue();
Loading history...
107
108
        if (!$this->cache->has($typeName)) {
109
            if ($node instanceof NamedTypeNode) {
110
                $definition = $this->getTypeDefinition($typeName);
111
112
                $this->cache->set(
113
                    $typeName,
114
                    null !== $definition ? $this->buildNamedType($definition) : $this->resolveType($node)
115
                );
116
            } else {
117
                $this->cache->set($typeName, $this->buildNamedType($node));
118
            }
119
        }
120
121
        return $this->cache->get($typeName);
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function buildDirective(DirectiveDefinitionNode $node): DirectiveInterface
128
    {
129
        return GraphQLDirective([
130
            'name'        => $node->getNameValue(),
131
            'description' => $node->getDescriptionValue(),
132
            'locations'   => array_map(function (NameNode $node) {
133
                return $node->getValue();
134
            }, $node->getLocations()),
135
            'arguments'   => $node->hasArguments() ? $this->buildArguments($node->getArguments()) : [],
136
            'astNode'     => $node,
137
        ]);
138
    }
139
140
    /**
141
     * @param TypeNodeInterface $typeNode
142
     * @return TypeInterface
143
     * @throws \Exception
144
     * @throws \TypeError
145
     */
146
    protected function buildWrappedType(TypeNodeInterface $typeNode): TypeInterface
147
    {
148
        $typeDefinition = $this->buildType(getNamedTypeNode($typeNode));
149
        return buildWrappedType($typeDefinition, $typeNode);
150
    }
151
152
    /**
153
     * @param FieldDefinitionNode|InputValueDefinitionNode $node
154
     * @return array
155
     * @throws \Exception
156
     * @throws \TypeError
157
     */
158
    protected function buildField($node): array
159
    {
160
        return [
161
            'type'              => $this->buildWrappedType($node->getType()),
162
            'description'       => $node->getDescriptionValue(),
163
            'arguments'         => $node->hasArguments() ? $this->buildArguments($node->getArguments()) : [],
0 ignored issues
show
Bug introduced by
The method hasArguments() does not exist on Digia\GraphQL\Language\A...nputValueDefinitionNode. ( Ignorable by Annotation )

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

163
            'arguments'         => $node->/** @scrutinizer ignore-call */ hasArguments() ? $this->buildArguments($node->getArguments()) : [],

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getArguments() does not exist on Digia\GraphQL\Language\A...nputValueDefinitionNode. ( Ignorable by Annotation )

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

163
            'arguments'         => $node->hasArguments() ? $this->buildArguments($node->/** @scrutinizer ignore-call */ getArguments()) : [],

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
164
            'deprecationReason' => getDeprecationReason($node),
165
            'astNode'           => $node,
166
        ];
167
    }
168
169
    /**
170
     * @param array $nodes
171
     * @return array
172
     */
173
    protected function buildArguments(array $nodes): array
174
    {
175
        return keyValMap(
176
            $nodes,
177
            function (InputValueDefinitionNode $value) {
178
                return $value->getNameValue();
179
            },
180
            function (InputValueDefinitionNode $value): array {
181
                $type = $this->buildWrappedType($value->getType());
182
                return [
183
                    'type'         => $type,
184
                    'description'  => $value->getDescriptionValue(),
185
                    'defaultValue' => valueFromAST($value->getDefaultValue(), $type),
186
                    'astNode'      => $value,
187
                ];
188
            });
189
    }
190
191
    /**
192
     * @param TypeDefinitionNodeInterface $node
193
     * @return NamedTypeInterface
194
     * @throws \Exception
195
     */
196
    protected function buildNamedType(TypeDefinitionNodeInterface $node): NamedTypeInterface
197
    {
198
        if ($node instanceof ObjectTypeDefinitionNode) {
199
            return $this->buildObjectType($node);
200
        }
201
        if ($node instanceof InterfaceTypeDefinitionNode) {
202
            return $this->buildInterfaceType($node);
203
        }
204
        if ($node instanceof EnumTypeDefinitionNode) {
205
            return $this->buildEnumType($node);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildEnumType($node) returns the type Digia\GraphQL\Type\Definition\EnumType which is incompatible with the type-hinted return Digia\GraphQL\Type\Definition\NamedTypeInterface.
Loading history...
206
        }
207
        if ($node instanceof UnionTypeDefinitionNode) {
208
            return $this->buildUnionType($node);
209
        }
210
        if ($node instanceof ScalarTypeDefinitionNode) {
211
            return $this->buildScalarType($node);
212
        }
213
        if ($node instanceof InputObjectTypeDefinitionNode) {
214
            return $this->buildInputObjectType($node);
215
        }
216
217
        throw new \Exception(sprintf('Type kind "%s" not supported.', $node->getKind()));
218
    }
219
220
    /**
221
     * @param ObjectTypeDefinitionNode $node
222
     * @return ObjectType
223
     */
224
    protected function buildObjectType(ObjectTypeDefinitionNode $node): ObjectType
225
    {
226
        return GraphQLObjectType([
227
            'name'        => $node->getNameValue(),
228
            'description' => $node->getDescriptionValue(),
229
            'fields'      => function () use ($node) {
230
                return $this->buildFields($node);
231
            },
232
            'interfaces'  => function () use ($node) {
233
                return $node->hasInterfaces() ? array_map(function (InterfaceTypeDefinitionNode $interface) {
234
                    return $this->buildType($interface);
235
                }, $node->getInterfaces()) : [];
236
            },
237
        ]);
238
    }
239
240
    /**
241
     * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|InputObjectTypeDefinitionNode $node
242
     * @return array
243
     */
244
    protected function buildFields($node): array
245
    {
246
        return $node->hasFields() ? keyValMap(
247
            $node->getFields(),
248
            function ($value) {
249
                return $value->getNameValue();
250
            },
251
            function ($value) {
252
                return $this->buildField($value);
253
            }
254
        ) : [];
255
    }
256
257
    /**
258
     * @param InterfaceTypeDefinitionNode $node
259
     * @return InterfaceType
260
     */
261
    protected function buildInterfaceType(InterfaceTypeDefinitionNode $node): InterfaceType
262
    {
263
        return GraphQLInterfaceType([
264
            'name'        => $node->getNameValue(),
265
            'description' => $node->getDescriptionValue(),
266
            'fields'      => function () use ($node): array {
267
                return $this->buildFields($node);
268
            },
269
            'astNode'     => $node,
270
        ]);
271
    }
272
273
    /**
274
     * @param EnumTypeDefinitionNode $node
275
     * @return EnumType
276
     */
277
    protected function buildEnumType(EnumTypeDefinitionNode $node): EnumType
278
    {
279
        return GraphQLEnumType([
280
            'name'        => $node->getNameValue(),
281
            'description' => $node->getDescriptionValue(),
282
            'values'      => $node->hasValues() ? keyValMap(
283
                $node->getValues(),
284
                function (EnumValueDefinitionNode $value): string {
285
                    return $value->getNameValue();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value->getNameValue() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
286
                },
287
                function (EnumValueDefinitionNode $value): array {
288
                    return [
289
                        'description'       => $value->getDescriptionValue(),
290
                        'deprecationReason' => getDeprecationReason($value),
291
                        'astNode'           => $value,
292
                    ];
293
                }
294
            ) : [],
295
            'astNode'     => $node,
296
        ]);
297
    }
298
299
    protected function buildUnionType(UnionTypeDefinitionNode $node): UnionType
300
    {
301
        return GraphQLUnionType([
302
            'name'        => $node->getNameValue(),
303
            'description' => $node->getDescriptionValue(),
304
            'types'       => $node->hasTypes() ? array_map(function (TypeNodeInterface $type) {
305
                return $this->buildType($type);
306
            }, $node->getTypes()) : [],
307
            'astNode'     => $node,
308
        ]);
309
    }
310
311
    /**
312
     * @param ScalarTypeDefinitionNode $node
313
     * @return ScalarType
314
     */
315
    protected function buildScalarType(ScalarTypeDefinitionNode $node): ScalarType
316
    {
317
        return GraphQLScalarType([
318
            'name'        => $node->getNameValue(),
319
            'description' => $node->getDescriptionValue(),
320
            'serialize'   => function ($value) {
321
                return $value;
322
            },
323
            'astNode'     => $node,
324
        ]);
325
    }
326
327
    /**
328
     * @param InputObjectTypeDefinitionNode $node
329
     * @return InputObjectType
330
     */
331
    protected function buildInputObjectType(InputObjectTypeDefinitionNode $node): InputObjectType
332
    {
333
        return GraphQLInputObjectType([
334
            'name'        => $node->getNameValue(),
335
            'description' => $node->getDescriptionValue(),
336
            'fields'      => $node->hasFields() ? keyValMap(
337
                $node->getFields(),
338
                function (InputValueDefinitionNode $value): string {
339
                    return $value->getNameValue();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value->getNameValue() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
340
                },
341
                function (InputValueDefinitionNode $value): array {
342
                    $type = $this->buildWrappedType($value->getType());
343
                    return [
344
                        'type'         => $type,
345
                        'description'  => $value->getDescriptionValue(),
346
                        'defaultValue' => valueFromAST($value->getDefaultValue(), $type),
347
                        'astNode'      => $value,
348
                    ];
349
                }) : [],
350
            'astNode'     => $node,
351
        ]);
352
    }
353
354
    /**
355
     * @inheritdoc
356
     */
357
    protected function resolveType(NodeInterface $node): TypeInterface
358
    {
359
        return call_user_func($this->resolveTypeFunction, $node);
360
    }
361
362
    /**
363
     * @param string $typeName
364
     * @return TypeDefinitionNodeInterface|null
365
     */
366
    protected function getTypeDefinition(string $typeName): ?TypeDefinitionNodeInterface
367
    {
368
        return $this->typeDefinitionsMap[$typeName] ?? null;
369
    }
370
}
371
372
/**
373
 * @param TypeNodeInterface $typeNode
374
 * @return NamedTypeNode
375
 */
376
function getNamedTypeNode(TypeNodeInterface $typeNode): NamedTypeNode
377
{
378
    $namedType = $typeNode;
379
380
    while ($namedType instanceof ListTypeNode || $namedType instanceof NonNullTypeNode) {
381
        $namedType = $namedType->getType();
382
    }
383
384
    return $namedType;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $namedType returns the type Digia\GraphQL\Language\AST\Node\TypeNodeInterface which includes types incompatible with the type-hinted return Digia\GraphQL\Language\AST\Node\NamedTypeNode.
Loading history...
385
}
386
387
/**
388
 * @param TypeInterface                        $innerType
389
 * @param NamedTypeInterface|TypeNodeInterface $inputTypeNode
390
 * @return TypeInterface
391
 * @throws \TypeError
392
 * @throws \Exception
393
 */
394
function buildWrappedType(TypeInterface $innerType, TypeNodeInterface $inputTypeNode): TypeInterface
395
{
396
    if ($inputTypeNode instanceof ListTypeNode) {
397
        return GraphQLList(buildWrappedType($innerType, $inputTypeNode->getType()));
398
    }
399
    if ($inputTypeNode instanceof NonNullTypeNode) {
400
        $wrappedType = buildWrappedType($innerType, $inputTypeNode->getType());
401
        return GraphQLNonNull(assertNullableType($wrappedType));
402
    }
403
404
    return $innerType;
405
}
406
407
/**
408
 * @param EnumValueDefinitionNode|FieldDefinitionNode $node
409
 * @return null|string
410
 * @throws \TypeError
411
 * @throws \Exception
412
 */
413
function getDeprecationReason(NodeInterface $node): ?string
414
{
415
    $deprecated = getDirectiveValues(GraphQLDeprecatedDirective(), $node);
416
    return $deprecated['reason'] ?? null;
417
}
418