Completed
Pull Request — master (#19)
by Christoffer
02:01
created

DefinitionBuilder::setTypeDefinitionMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Language\AST\Schema;
4
5
use Digia\GraphQL\Language\AST\Node\Contract\NodeInterface;
6
use Digia\GraphQL\Language\AST\Node\Contract\TypeDefinitionNodeInterface;
7
use Digia\GraphQL\Language\AST\Node\Contract\TypeNodeInterface;
8
use Digia\GraphQL\Language\AST\Node\DirectiveDefinitionNode;
9
use Digia\GraphQL\Language\AST\Node\EnumTypeDefinitionNode;
10
use Digia\GraphQL\Language\AST\Node\EnumValueDefinitionNode;
11
use Digia\GraphQL\Language\AST\Node\FieldDefinitionNode;
12
use Digia\GraphQL\Language\AST\Node\InputObjectTypeDefinitionNode;
13
use Digia\GraphQL\Language\AST\Node\InputValueDefinitionNode;
14
use Digia\GraphQL\Language\AST\Node\InterfaceTypeDefinitionNode;
15
use Digia\GraphQL\Language\AST\Node\ListTypeNode;
16
use Digia\GraphQL\Language\AST\Node\NamedTypeNode;
17
use Digia\GraphQL\Language\AST\Node\NameNode;
18
use Digia\GraphQL\Language\AST\Node\NonNullTypeNode;
19
use Digia\GraphQL\Language\AST\Node\ObjectTypeDefinitionNode;
20
use Digia\GraphQL\Language\AST\Node\ScalarTypeDefinitionNode;
21
use Digia\GraphQL\Language\AST\Node\UnionTypeDefinitionNode;
22
use Digia\GraphQL\Language\AST\Schema\Builder\Contract\BuilderInterface;
0 ignored issues
show
Bug introduced by
The type Digia\GraphQL\Language\A...ntract\BuilderInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use Digia\GraphQL\Language\AST\Schema\Builder\Contract\DirectorInterface;
0 ignored issues
show
Bug introduced by
The type Digia\GraphQL\Language\A...tract\DirectorInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use Digia\GraphQL\Language\AST\Schema\Contract\DefinitionBuilderInterface;
25
use Digia\GraphQL\Type\Definition\Contract\DirectiveInterface;
26
use Digia\GraphQL\Type\Definition\Contract\NamedTypeInterface;
27
use Digia\GraphQL\Type\Definition\Contract\TypeInterface;
28
use Digia\GraphQL\Type\Definition\EnumType;
29
use Digia\GraphQL\Type\Definition\InputObjectType;
30
use Digia\GraphQL\Type\Definition\InterfaceType;
31
use Digia\GraphQL\Type\Definition\ObjectType;
32
use Digia\GraphQL\Type\Definition\ScalarType;
33
use Digia\GraphQL\Type\Definition\UnionType;
34
use function Digia\GraphQL\Type\assertNullableType;
35
use function Digia\GraphQL\Type\GraphQLDirective;
36
use function Digia\GraphQL\Type\GraphQLEnumType;
37
use function Digia\GraphQL\Type\GraphQLInputObjectType;
38
use function Digia\GraphQL\Type\GraphQLInterfaceType;
39
use function Digia\GraphQL\Type\GraphQLList;
40
use function Digia\GraphQL\Type\GraphQLNonNull;
41
use function Digia\GraphQL\Type\GraphQLObjectType;
42
use function Digia\GraphQL\Type\GraphQLScalarType;
43
use function Digia\GraphQL\Type\GraphQLUnionType;
44
use function Digia\GraphQL\Type\specifiedScalarTypes;
45
use function Digia\GraphQL\Util\keyMap;
46
use function Digia\GraphQL\Util\keyValMap;
47
48
class DefinitionBuilder implements DefinitionBuilderInterface
49
{
50
51
    /**
52
     * @var array
53
     */
54
    protected $_cache = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $typeDefinitionsMap;
60
61
    /**
62
     * @var ?callable
63
     */
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...
64
    protected $resolveTypeFunction;
65
66
    /**
67
     * DefinitionBuilder constructor.
68
     *
69
     * @param callable $resolveTypeFunction
70
     */
71
    public function __construct(callable $resolveTypeFunction)
72
    {
73
        $this->typeDefinitionsMap  = [];
74
        $this->resolveTypeFunction = $resolveTypeFunction;
75
        $this->_cache              = keyMap(
76
            array_merge(specifiedScalarTypes()/*, introspectionTypes()*/),
77
            function (NamedTypeInterface $type) {
78
                return $type->getName();
79
            }
80
        );
81
    }
82
83
    /**
84
     * @inheritdoc
85
     */
86
    public function setTypeDefinitionMap(array $typeDefinitionMap)
87
    {
88
        $this->typeDefinitionsMap = $typeDefinitionMap;
89
        return $this;
90
    }
91
92
    /**
93
     * @param NamedTypeNode|TypeDefinitionNodeInterface $node
94
     * @inheritdoc
95
     */
96
    public function buildType(NodeInterface $node): TypeInterface
97
    {
98
        $typeName = $node->getNameValue();
0 ignored issues
show
Bug introduced by
The method getNameValue() does not exist on Digia\GraphQL\Language\A...\Contract\NodeInterface. It seems like you code against a sub-type of Digia\GraphQL\Language\A...\Contract\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...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...DefinitionNodeInterface or Digia\GraphQL\Language\A...\VariableDefinitionNode 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\FieldNode or Digia\GraphQL\Language\AST\Node\VariableNode 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

98
        /** @scrutinizer ignore-call */ 
99
        $typeName = $node->getNameValue();
Loading history...
99
100
        if (!$this->isInCache($typeName)) {
101
            if ($node instanceof NamedTypeNode) {
102
                $definition = $this->getTypeDefinition($typeName);
103
104
                $this->setInCache(
105
                    $typeName,
106
                    null !== $definition ? $this->buildNamedType($definition) : $this->resolveType($node)
107
                );
108
            } else {
109
                $this->setInCache($typeName, $this->buildNamedType($node));
110
            }
111
        }
112
113
        return $this->getFromCache($typeName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getFromCache($typeName) could return the type null which is incompatible with the type-hinted return Digia\GraphQL\Type\Defin...\Contract\TypeInterface. Consider adding an additional type-check to rule them out.
Loading history...
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function buildDirective(DirectiveDefinitionNode $node): DirectiveInterface
120
    {
121
        return GraphQLDirective([
122
            'name'        => $node->getNameValue(),
123
            'description' => $node->getDescriptionValue(),
124
            'locations'   => array_map(function (NameNode $node) {
125
                return $node->getValue();
126
            }, $node->getLocations()),
127
            'arguments'   => $node->hasArguments() ? $this->buildArguments($node->getArguments()) : [],
128
            'astNode'     => $node,
129
        ]);
130
    }
131
132
    /**
133
     * @param TypeNodeInterface $typeNode
134
     * @return TypeInterface
135
     * @throws \Exception
136
     * @throws \TypeError
137
     */
138
    protected function buildWrappedType(TypeNodeInterface $typeNode): TypeInterface
139
    {
140
        $typeDefinition = $this->buildType(getNamedTypeNode($typeNode));
141
        return buildWrappedType($typeDefinition, $typeNode);
142
    }
143
144
    /**
145
     * @param FieldDefinitionNode|InputValueDefinitionNode $node
146
     * @return array
147
     * @throws \Exception
148
     * @throws \TypeError
149
     */
150
    protected function buildField($node): array
151
    {
152
        return [
153
            'type'              => $this->buildWrappedType($node->getType()),
154
            'description'       => $node->getDescriptionValue(),
155
            'arguments'         => $node->hasArguments() ? $this->buildArguments($node->getArguments()) : [],
0 ignored issues
show
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

155
            '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...
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

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