Failed Conditions
Push — master ( 804daa...8817e8 )
by Vladimir
04:14
created

SchemaValidationContext::getFieldArgTypeNode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 3
crap 2
1
<?php
2
namespace GraphQL\Type;
3
4
use GraphQL\Error\Error;
5
use GraphQL\Language\AST\EnumValueDefinitionNode;
6
use GraphQL\Language\AST\FieldDefinitionNode;
7
use GraphQL\Language\AST\InputValueDefinitionNode;
8
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
9
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
10
use GraphQL\Language\AST\NamedTypeNode;
11
use GraphQL\Language\AST\Node;
12
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
13
use GraphQL\Language\AST\ObjectTypeExtensionNode;
14
use GraphQL\Language\AST\SchemaDefinitionNode;
15
use GraphQL\Language\AST\TypeDefinitionNode;
16
use GraphQL\Language\AST\TypeNode;
17
use GraphQL\Type\Definition\Directive;
18
use GraphQL\Type\Definition\EnumType;
19
use GraphQL\Type\Definition\EnumValueDefinition;
20
use GraphQL\Type\Definition\FieldDefinition;
21
use GraphQL\Type\Definition\InputObjectField;
22
use GraphQL\Type\Definition\InputObjectType;
23
use GraphQL\Type\Definition\InterfaceType;
24
use GraphQL\Type\Definition\NamedType;
25
use GraphQL\Type\Definition\NonNull;
26
use GraphQL\Type\Definition\ObjectType;
27
use GraphQL\Type\Definition\Type;
28
use GraphQL\Type\Definition\UnionType;
29
use GraphQL\Utils\TypeComparators;
30
use GraphQL\Utils\Utils;
31
32
class SchemaValidationContext
33
{
34
    /**
35
     * @var Error[]
36
     */
37
    private $errors = [];
38
39
    /**
40
     * @var Schema
41
     */
42
    private $schema;
43
44 76
    public function __construct(Schema $schema)
45
    {
46 76
        $this->schema = $schema;
47 76
    }
48
49
    /**
50
     * @return Error[]
51
     */
52 76
    public function getErrors()
53
    {
54 76
        return $this->errors;
55
    }
56
57 76
    public function validateRootTypes()
58
    {
59 76
        $queryType = $this->schema->getQueryType();
60 76
        if (!$queryType) {
0 ignored issues
show
introduced by
$queryType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
61 2
            $this->reportError(
62 2
                'Query root type must be provided.',
63 2
                $this->schema->getAstNode()
64
            );
65 74
        } else if (!$queryType instanceof ObjectType) {
0 ignored issues
show
introduced by
$queryType is always a sub-type of GraphQL\Type\Definition\ObjectType.
Loading history...
66 1
            $this->reportError(
67 1
                'Query root type must be Object type, it cannot be ' . Utils::printSafe($queryType) . '.',
68 1
                $this->getOperationTypeNode($queryType, 'query')
69
            );
70
        }
71
72 76
        $mutationType = $this->schema->getMutationType();
73 76
        if ($mutationType && !$mutationType instanceof ObjectType) {
0 ignored issues
show
introduced by
$mutationType is always a sub-type of GraphQL\Type\Definition\ObjectType.
Loading history...
74 1
            $this->reportError(
75 1
                'Mutation root type must be Object type if provided, it cannot be ' . Utils::printSafe($mutationType) . '.',
76 1
                $this->getOperationTypeNode($mutationType, 'mutation')
77
            );
78
        }
79
80 76
        $subscriptionType = $this->schema->getSubscriptionType();
81 76
        if ($subscriptionType && !$subscriptionType instanceof ObjectType) {
0 ignored issues
show
introduced by
$subscriptionType is always a sub-type of GraphQL\Type\Definition\ObjectType.
Loading history...
82 1
            $this->reportError(
83 1
                'Subscription root type must be Object type if provided, it cannot be ' . Utils::printSafe($subscriptionType) . '.',
84 1
                $this->getOperationTypeNode($subscriptionType, 'subscription')
85
            );
86
        }
87 76
    }
88
89
    /**
90
     * @param Type $type
91
     * @param string $operation
92
     *
93
     * @return TypeNode|TypeDefinitionNode
94
     */
95 3
    private function getOperationTypeNode($type, $operation)
96
    {
97 3
        $astNode = $this->schema->getAstNode();
98
99 3
        $operationTypeNode = null;
100 3
        if ($astNode instanceof SchemaDefinitionNode) {
0 ignored issues
show
introduced by
$astNode is always a sub-type of GraphQL\Language\AST\SchemaDefinitionNode.
Loading history...
101 3
            $operationTypeNode = null;
102
103 3
            foreach($astNode->operationTypes as $operationType) {
104 3
                if ($operationType->operation === $operation) {
105 3
                    $operationTypeNode = $operationType;
106 3
                    break;
107
                }
108
            }
109
        }
110
111 3
        return $operationTypeNode ? $operationTypeNode->type : ($type ? $type->astNode : null);
0 ignored issues
show
introduced by
$type is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
112
    }
113
114 76
    public function validateDirectives()
115
    {
116 76
        $directives = $this->schema->getDirectives();
117 76
        foreach($directives as $directive) {
118
            // Ensure all directives are in fact GraphQL directives.
119 76
            if (!$directive instanceof Directive) {
120 1
                $this->reportError(
121 1
                    "Expected directive but got: " . Utils::printSafe($directive) . '.',
122 1
                    is_object($directive) ? $directive->astNode : null
123
                );
124 1
                continue;
125
            }
126
127
            // Ensure they are named correctly.
128 75
            $this->validateName($directive);
129
130
            // TODO: Ensure proper locations.
131
132 75
            $argNames = [];
133 75
            foreach ($directive->args as $arg) {
134 75
                $argName = $arg->name;
135
136
                // Ensure they are named correctly.
137 75
                $this->validateName($directive);
138
139 75
                if (isset($argNames[$argName])) {
140
                    $this->reportError(
141
                        "Argument @{$directive->name}({$argName}:) can only be defined once.",
142
                        $this->getAllDirectiveArgNodes($directive, $argName)
143
                    );
144
                    continue;
145
                }
146
147 75
                $argNames[$argName] = true;
148
149
                // Ensure the type is an input type.
150 75
                if (!Type::isInputType($arg->getType())) {
151
                    $this->reportError(
152
                        "The type of @{$directive->name}({$argName}:) must be Input Type " .
153
                        'but got: ' . Utils::printSafe($arg->getType()) . '.',
154 75
                        $this->getDirectiveArgTypeNode($directive, $argName)
155
                    );
156
                }
157
            }
158
        }
159 76
    }
160
161
    /**
162
     * @param Type|Directive|FieldDefinition|EnumValueDefinition|InputObjectField $node
163
     */
164 76
    private function validateName($node)
165
    {
166
        // Ensure names are valid, however introspection types opt out.
167 76
        $error = Utils::isValidNameError($node->name, $node->astNode);
168 76
        if ($error && !Introspection::isIntrospectionType($node)) {
0 ignored issues
show
Bug introduced by
It seems like $node can also be of type GraphQL\Type\Definition\InputObjectField and GraphQL\Type\Definition\Directive and GraphQL\Type\Definition\FieldDefinition and GraphQL\Type\Definition\EnumValueDefinition; however, parameter $type of GraphQL\Type\Introspection::isIntrospectionType() does only seem to accept GraphQL\Type\Definition\Type, maybe add an additional type check? ( Ignorable by Annotation )

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

168
        if ($error && !Introspection::isIntrospectionType(/** @scrutinizer ignore-type */ $node)) {
Loading history...
169 5
            $this->addError($error);
170
        }
171 76
    }
172
173 76
    public function validateTypes()
174
    {
175 76
        $typeMap = $this->schema->getTypeMap();
176 76
        foreach($typeMap as $typeName => $type) {
177
            // Ensure all provided types are in fact GraphQL type.
178 76
            if (!$type instanceof NamedType) {
179
                $this->reportError(
180
                    "Expected GraphQL named type but got: " . Utils::printSafe($type) . '.',
181
                    is_object($type) ? $type->astNode : null
182
                );
183
                continue;
184
            }
185
186 76
            $this->validateName($type);
187
188 76
            if ($type instanceof ObjectType) {
189
                // Ensure fields are valid
190 76
                $this->validateFields($type);
191
192
                // Ensure objects implement the interfaces they claim to.
193 76
                $this->validateObjectInterfaces($type);
194 76
            } else if ($type instanceof InterfaceType) {
195
                // Ensure fields are valid.
196 30
                $this->validateFields($type);
197 76
            } else if ($type instanceof UnionType) {
198
                // Ensure Unions include valid member types.
199 11
                $this->validateUnionMembers($type);
200 76
            } else if ($type instanceof EnumType) {
201
                // Ensure Enums have valid values.
202 76
                $this->validateEnumValues($type);
203 76
            } else if ($type instanceof InputObjectType) {
204
                // Ensure Input Object fields are valid.
205 76
                $this->validateInputFields($type);
206
            }
207
        }
208 76
    }
209
210
    /**
211
     * @param ObjectType|InterfaceType $type
212
     */
213 76
    private function validateFields($type)
214
    {
215 76
        $fieldMap = $type->getFields();
216
217
        // Objects and Interfaces both must define one or more fields.
218 76
        if (!$fieldMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldMap of type GraphQL\Type\Definition\FieldDefinition[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
219 1
            $this->reportError(
220 1
                "Type {$type->name} must define one or more fields.",
221 1
                $this->getAllObjectOrInterfaceNodes($type)
222
            );
223
        }
224
225 76
        foreach ($fieldMap as $fieldName => $field) {
226
            // Ensure they are named correctly.
227 76
            $this->validateName($field);
228
229
            // Ensure they were defined at most once.
230 76
            $fieldNodes = $this->getAllFieldNodes($type, $fieldName);
231 76
            if ($fieldNodes && count($fieldNodes) > 1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldNodes of type GraphQL\Language\AST\FieldDefinitionNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
232
                $this->reportError(
233
                    "Field {$type->name}.{$fieldName} can only be defined once.",
234
                    $fieldNodes
235
                );
236
                continue;
237
            }
238
239
            // Ensure the type is an output type
240 76
            if (!Type::isOutputType($field->getType())) {
241 6
                $this->reportError(
242 6
                    "The type of {$type->name}.{$fieldName} must be Output Type " .
243 6
                    'but got: ' . Utils::printSafe($field->getType()) . '.',
244 6
                    $this->getFieldTypeNode($type, $fieldName)
245
                );
246
            }
247
248
            // Ensure the arguments are valid
249 76
            $argNames = [];
250 76
            foreach($field->args as $arg) {
251 76
                $argName = $arg->name;
252
253
                // Ensure they are named correctly.
254 76
                $this->validateName($arg);
255
256 76
                if (isset($argNames[$argName])) {
257
                    $this->reportError(
258
                        "Field argument {$type->name}.{$fieldName}({$argName}:) can only " .
259
                        'be defined once.',
260
                        $this->getAllFieldArgNodes($type, $fieldName, $argName)
261
                    );
262
                }
263 76
                $argNames[$argName] = true;
264
265
                // Ensure the type is an input type
266 76
                if (!Type::isInputType($arg->getType())) {
267 3
                    $this->reportError(
268 3
                        "The type of {$type->name}.{$fieldName}({$argName}:) must be Input " .
269 3
                        'Type but got: '. Utils::printSafe($arg->getType()) . '.',
270 76
                        $this->getFieldArgTypeNode($type, $fieldName, $argName)
271
                    );
272
                }
273
            }
274
        }
275 76
    }
276
277 76
    private function validateObjectInterfaces(ObjectType $object)
278
    {
279 76
        $implementedTypeNames = [];
280 76
        foreach($object->getInterfaces() as $iface) {
281 29
            if (! $iface instanceof InterfaceType) {
282 2
                $this->reportError(
283 2
                    sprintf(
284
                        'Type %s must only implement Interface types, ' .
285 2
                        'it cannot implement %s.',
286 2
                        $object->name,
287 2
                        Utils::printSafe($iface)
288
                    ),
289 2
                    $this->getImplementsInterfaceNode($object, $iface)
290
                );
291 2
                continue;
292
            }
293 27
            if (isset($implementedTypeNames[$iface->name])) {
294 1
                $this->reportError(
295 1
                    "Type {$object->name} can only implement {$iface->name} once.",
296 1
                    $this->getAllImplementsInterfaceNodes($object, $iface)
297
                );
298 1
                continue;
299
            }
300 27
            $implementedTypeNames[$iface->name] = true;
301 27
            $this->validateObjectImplementsInterface($object, $iface);
302
        }
303 76
    }
304
305
    /**
306
     * @param ObjectType $object
307
     * @param InterfaceType $iface
308
     */
309 27
    private function validateObjectImplementsInterface(ObjectType $object, $iface)
310
    {
311 27
        $objectFieldMap = $object->getFields();
312 27
        $ifaceFieldMap = $iface->getFields();
313
314
        // Assert each interface field is implemented.
315 27
        foreach ($ifaceFieldMap as $fieldName => $ifaceField) {
316 27
            $objectField = array_key_exists($fieldName, $objectFieldMap)
317 26
                ? $objectFieldMap[$fieldName]
318 27
                : null;
319
320
            // Assert interface field exists on object.
321 27
            if (!$objectField) {
322 1
                $this->reportError(
323 1
                    "Interface field {$iface->name}.{$fieldName} expected but " .
324 1
                    "{$object->name} does not provide it.",
325 1
                    [$this->getFieldNode($iface, $fieldName), $object->astNode]
326
                );
327 1
                continue;
328
            }
329
330
            // Assert interface field type is satisfied by object field type, by being
331
            // a valid subtype. (covariant)
332
            if (
333 26
                !TypeComparators::isTypeSubTypeOf(
334 26
                    $this->schema,
335 26
                    $objectField->getType(),
0 ignored issues
show
Bug introduced by
$objectField->getType() of type GraphQL\Type\Definition\Type is incompatible with the type GraphQL\Type\Definition\AbstractType expected by parameter $maybeSubType of GraphQL\Utils\TypeComparators::isTypeSubTypeOf(). ( Ignorable by Annotation )

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

335
                    /** @scrutinizer ignore-type */ $objectField->getType(),
Loading history...
336 26
                    $ifaceField->getType()
0 ignored issues
show
Bug introduced by
$ifaceField->getType() of type GraphQL\Type\Definition\Type is incompatible with the type GraphQL\Type\Definition\AbstractType expected by parameter $superType of GraphQL\Utils\TypeComparators::isTypeSubTypeOf(). ( Ignorable by Annotation )

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

336
                    /** @scrutinizer ignore-type */ $ifaceField->getType()
Loading history...
337
                )
338
            ) {
339 6
                $this->reportError(
340 6
                    "Interface field {$iface->name}.{$fieldName} expects type ".
341 6
                    "{$ifaceField->getType()} but {$object->name}.{$fieldName} " .
342 6
                    "is type " . Utils::printSafe($objectField->getType()) . ".",
343
                    [
344 6
                        $this->getFieldTypeNode($iface, $fieldName),
345 6
                        $this->getFieldTypeNode($object, $fieldName),
346
                    ]
347
                );
348
            }
349
350
            // Assert each interface field arg is implemented.
351 26
            foreach($ifaceField->args as $ifaceArg) {
352 8
                $argName = $ifaceArg->name;
353 8
                $objectArg = null;
354
355 8
                foreach($objectField->args as $arg) {
356 7
                    if ($arg->name === $argName) {
357 7
                        $objectArg = $arg;
358 7
                        break;
359
                    }
360
                }
361
362
                // Assert interface field arg exists on object field.
363 8
                if (!$objectArg) {
364 1
                    $this->reportError(
365 1
                        "Interface field argument {$iface->name}.{$fieldName}({$argName}:) " .
366 1
                        "expected but {$object->name}.{$fieldName} does not provide it.",
367
                        [
368 1
                            $this->getFieldArgNode($iface, $fieldName, $argName),
369 1
                            $this->getFieldNode($object, $fieldName),
370
                        ]
371
                    );
372 1
                    continue;
373
                }
374
375
                // Assert interface field arg type matches object field arg type.
376
                // (invariant)
377
                // TODO: change to contravariant?
378 7
                if (!TypeComparators::isEqualType($ifaceArg->getType(), $objectArg->getType())) {
379 2
                    $this->reportError(
380 2
                        "Interface field argument {$iface->name}.{$fieldName}({$argName}:) ".
381 2
                        "expects type " . Utils::printSafe($ifaceArg->getType()) . " but " .
382 2
                        "{$object->name}.{$fieldName}({$argName}:) is type " .
383 2
                        Utils::printSafe($objectArg->getType()) . ".",
384
                        [
385 2
                            $this->getFieldArgTypeNode($iface, $fieldName, $argName),
386 7
                            $this->getFieldArgTypeNode($object, $fieldName, $argName),
387
                        ]
388
                    );
389
                }
390
391
                // TODO: validate default values?
392
            }
393
394
            // Assert additional arguments must not be required.
395 26
            foreach($objectField->args as $objectArg) {
396 7
                $argName = $objectArg->name;
397 7
                $ifaceArg = null;
398
399 7
                foreach($ifaceField->args as $arg) {
400 7
                    if ($arg->name === $argName) {
401 7
                        $ifaceArg = $arg;
402 7
                        break;
403
                    }
404
                }
405
406 7
                if (!$ifaceArg && $objectArg->getType() instanceof NonNull) {
407 1
                    $this->reportError(
408 1
                        "Object field argument {$object->name}.{$fieldName}({$argName}:) " .
409 1
                        "is of required type " . Utils::printSafe($objectArg->getType()) . " but is not also " .
410 1
                        "provided by the Interface field {$iface->name}.{$fieldName}.",
411
                        [
412 1
                            $this->getFieldArgTypeNode($object, $fieldName, $argName),
413 26
                            $this->getFieldNode($iface, $fieldName),
414
                        ]
415
                    );
416
                }
417
            }
418
        }
419 27
    }
420
421 11
    private function validateUnionMembers(UnionType $union)
422
    {
423 11
        $memberTypes = $union->getTypes();
424
425 11
        if (!$memberTypes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $memberTypes of type GraphQL\Type\Definition\ObjectType[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
426 1
            $this->reportError(
427 1
                "Union type {$union->name} must define one or more member types.",
428 1
                $union->astNode
429
            );
430
        }
431
432 11
        $includedTypeNames = [];
433
434 11
        foreach($memberTypes as $memberType) {
435 10
            if (isset($includedTypeNames[$memberType->name])) {
436 1
                $this->reportError(
437 1
                    "Union type {$union->name} can only include type ".
438 1
                    "{$memberType->name} once.",
439 1
                    $this->getUnionMemberTypeNodes($union, $memberType->name)
440
                );
441 1
                continue;
442
            }
443 10
            $includedTypeNames[$memberType->name] = true;
444 10
            if (!$memberType instanceof ObjectType) {
445 1
                $this->reportError(
446 1
                    "Union type {$union->name} can only include Object types, ".
447 1
                    "it cannot include " . Utils::printSafe($memberType) . ".",
448 10
                    $this->getUnionMemberTypeNodes($union, Utils::printSafe($memberType))
449
                );
450
            }
451
        }
452 11
    }
453
454 76
    private function validateEnumValues(EnumType $enumType)
455
    {
456 76
        $enumValues = $enumType->getValues();
457
458 76
        if (!$enumValues) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $enumValues of type GraphQL\Type\Definition\EnumValueDefinition[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
459 1
            $this->reportError(
460 1
                "Enum type {$enumType->name} must define one or more values.",
461 1
                $enumType->astNode
462
            );
463
        }
464
465 76
        foreach($enumValues as $enumValue) {
466 76
            $valueName = $enumValue->name;
467
468
            // Ensure no duplicates
469 76
            $allNodes = $this->getEnumValueNodes($enumType, $valueName);
470 76
            if ($allNodes && count($allNodes) > 1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allNodes of type GraphQL\Language\AST\EnumValueDefinitionNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
471 1
                $this->reportError(
472 1
                    "Enum type {$enumType->name} can include value {$valueName} only once.",
473 1
                    $allNodes
474
                );
475
            }
476
477
            // Ensure valid name.
478 76
            $this->validateName($enumValue);
479 76
            if ($valueName === 'true' || $valueName === 'false' || $valueName === 'null') {
480 3
                $this->reportError(
481 3
                    "Enum type {$enumType->name} cannot include value: {$valueName}.",
482 76
                    $enumValue->astNode
483
                );
484
            }
485
        }
486 76
    }
487
488 17
    private function validateInputFields(InputObjectType $inputObj)
489
    {
490 17
        $fieldMap = $inputObj->getFields();
491
492 17
        if (!$fieldMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldMap of type GraphQL\Type\Definition\InputObjectField[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
493 1
            $this->reportError(
494 1
                "Input Object type {$inputObj->name} must define one or more fields.",
495 1
                $inputObj->astNode
496
            );
497
        }
498
499
        // Ensure the arguments are valid
500 17
        foreach ($fieldMap as $fieldName => $field) {
501
            // Ensure they are named correctly.
502 16
            $this->validateName($field);
503
504
            // TODO: Ensure they are unique per field.
505
506
            // Ensure the type is an input type
507 16
            if (!Type::isInputType($field->getType())) {
0 ignored issues
show
Bug introduced by
It seems like $field->getType() can also be of type callable; however, parameter $type of GraphQL\Type\Definition\Type::isInputType() does only seem to accept GraphQL\Type\Definition\Type, maybe add an additional type check? ( Ignorable by Annotation )

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

507
            if (!Type::isInputType(/** @scrutinizer ignore-type */ $field->getType())) {
Loading history...
508 4
                $this->reportError(
509 4
                    "The type of {$inputObj->name}.{$fieldName} must be Input Type " .
510 4
                    "but got: " . Utils::printSafe($field->getType()) . ".",
511 16
                    $field->astNode ? $field->astNode->type : null
512
                );
513
            }
514
        }
515 17
    }
516
517
    /**
518
     * @param ObjectType|InterfaceType $type
519
     * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]|InterfaceTypeExtensionNode[]
520
     */
521 76
    private function getAllObjectOrInterfaceNodes($type)
522
    {
523 76
        return $type->astNode
524 41
            ? ($type->extensionASTNodes
525
                ? array_merge([$type->astNode], $type->extensionASTNodes)
526 41
                : [$type->astNode])
527 76
            : ($type->extensionASTNodes ?: []);
528
    }
529
530
    /**
531
     * @param ObjectType $type
532
     * @param InterfaceType $iface
533
     * @return NamedTypeNode|null
534
     */
535 2
    private function getImplementsInterfaceNode(ObjectType $type, $iface)
536
    {
537 2
        $nodes = $this->getAllImplementsInterfaceNodes($type, $iface);
538 2
        return $nodes && isset($nodes[0]) ? $nodes[0] : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $nodes of type GraphQL\Language\AST\NamedTypeNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
539
    }
540
541
    /**
542
     * @param ObjectType $type
543
     * @param InterfaceType $iface
544
     * @return NamedTypeNode[]
545
     */
546 3
    private function getAllImplementsInterfaceNodes(ObjectType $type, $iface)
547
    {
548 3
        $implementsNodes = [];
549 3
        $astNodes = $this->getAllObjectOrInterfaceNodes($type);
550
551 3
        foreach($astNodes as $astNode) {
552 2
            if ($astNode && $astNode->interfaces) {
553 2
                foreach($astNode->interfaces as $node) {
554 2
                    if ($node->name->value === $iface->name) {
555 2
                        $implementsNodes[] = $node;
556
                    }
557
                }
558
            }
559
        }
560
561 3
        return $implementsNodes;
562
    }
563
564
    /**
565
     * @param ObjectType|InterfaceType $type
566
     * @param string $fieldName
567
     * @return FieldDefinitionNode|null
568
     */
569 19
    private function getFieldNode($type, $fieldName)
570
    {
571 19
        $nodes = $this->getAllFieldNodes($type, $fieldName);
572 19
        return $nodes && isset($nodes[0]) ? $nodes[0] : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $nodes of type GraphQL\Language\AST\FieldDefinitionNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
573
    }
574
575
    /**
576
     * @param ObjectType|InterfaceType $type
577
     * @param string $fieldName
578
     * @return FieldDefinitionNode[]
579
     */
580 76
    private function getAllFieldNodes($type, $fieldName)
581
    {
582 76
        $fieldNodes = [];
583 76
        $astNodes = $this->getAllObjectOrInterfaceNodes($type);
584 76
        foreach($astNodes as $astNode) {
585 41
            if ($astNode && $astNode->fields) {
586 41
                foreach($astNode->fields as $node) {
587 41
                    if ($node->name->value === $fieldName) {
588 41
                        $fieldNodes[] = $node;
589
                    }
590
                }
591
            }
592
        }
593 76
        return $fieldNodes;
594
    }
595
596
    /**
597
     * @param ObjectType|InterfaceType $type
598
     * @param string $fieldName
599
     * @return TypeNode|null
600
     */
601 12
    private function getFieldTypeNode($type, $fieldName)
602
    {
603 12
        $fieldNode = $this->getFieldNode($type, $fieldName);
604 12
        return $fieldNode ? $fieldNode->type : null;
605
    }
606
607
    /**
608
     * @param ObjectType|InterfaceType $type
609
     * @param string $fieldName
610
     * @param string $argName
611
     * @return InputValueDefinitionNode|null
612
     */
613 7
    private function getFieldArgNode($type, $fieldName, $argName)
614
    {
615 7
        $nodes = $this->getAllFieldArgNodes($type, $fieldName, $argName);
616 7
        return $nodes && isset($nodes[0]) ? $nodes[0] : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $nodes of type GraphQL\Language\AST\InputValueDefinitionNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
617
    }
618
619
    /**
620
     * @param ObjectType|InterfaceType $type
621
     * @param string $fieldName
622
     * @param string $argName
623
     * @return InputValueDefinitionNode[]
624
     */
625 7
    private function getAllFieldArgNodes($type, $fieldName, $argName)
626
    {
627 7
        $argNodes = [];
628 7
        $fieldNode = $this->getFieldNode($type, $fieldName);
629 7
        if ($fieldNode && $fieldNode->arguments) {
630 5
            foreach ($fieldNode->arguments as $node) {
631 5
                if ($node->name->value === $argName) {
632 5
                    $argNodes[] = $node;
633
                }
634
            }
635
        }
636 7
        return $argNodes;
637
    }
638
639
    /**
640
     * @param ObjectType|InterfaceType $type
641
     * @param string $fieldName
642
     * @param string $argName
643
     * @return TypeNode|null
644
     */
645 6
    private function getFieldArgTypeNode($type, $fieldName, $argName)
646
    {
647 6
        $fieldArgNode = $this->getFieldArgNode($type, $fieldName, $argName);
648 6
        return $fieldArgNode ? $fieldArgNode->type : null;
649
    }
650
651
    /**
652
     * @param Directive $directive
653
     * @param string $argName
654
     * @return InputValueDefinitionNode[]
655
     */
656
    private function getAllDirectiveArgNodes(Directive $directive, $argName)
657
    {
658
        $argNodes = [];
659
        $directiveNode = $directive->astNode;
660
        if ($directiveNode && $directiveNode->arguments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $directiveNode->arguments of type GraphQL\Language\AST\ArgumentNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
661
            foreach($directiveNode->arguments as $node) {
662
                if ($node->name->value === $argName) {
663
                    $argNodes[] = $node;
664
                }
665
            }
666
        }
667
668
        return $argNodes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $argNodes returns an array which contains values of type GraphQL\Language\AST\ArgumentNode which are incompatible with the documented value type GraphQL\Language\AST\InputValueDefinitionNode.
Loading history...
669
    }
670
671
    /**
672
     * @param Directive $directive
673
     * @param string $argName
674
     * @return TypeNode|null
675
     */
676
    private function getDirectiveArgTypeNode(Directive $directive, $argName)
677
    {
678
        $argNode = $this->getAllDirectiveArgNodes($directive, $argName)[0];
679
        return $argNode ? $argNode->type : null;
0 ignored issues
show
introduced by
$argNode is of type GraphQL\Language\AST\InputValueDefinitionNode, thus it always evaluated to true.
Loading history...
680
    }
681
682
    /**
683
     * @param UnionType $union
684
     * @param string $typeName
685
     * @return NamedTypeNode[]
686
     */
687 2
    private function getUnionMemberTypeNodes(UnionType $union, $typeName)
688
    {
689 2
        if ($union->astNode && $union->astNode->types) {
690 2
            return array_filter(
691 2
                $union->astNode->types,
692
                function (NamedTypeNode $value) use ($typeName) {
693 2
                    return $value->name->value === $typeName;
694 2
                }
695
            );
696
        }
697 1
        return $union->astNode ?
698 1
            $union->astNode->types : null;
699
    }
700
701
    /**
702
     * @param EnumType $enum
703
     * @param string $valueName
704
     * @return EnumValueDefinitionNode[]
705
     */
706 76
    private function getEnumValueNodes(EnumType $enum, $valueName)
707
    {
708 76
        if ($enum->astNode && $enum->astNode->values) {
709 1
            return array_filter(
710 1
                iterator_to_array($enum->astNode->values),
0 ignored issues
show
Bug introduced by
It seems like $enum->astNode->values can also be of type GraphQL\Language\AST\EnumValueDefinitionNode[]; however, parameter $iterator of iterator_to_array() does only seem to accept Traversable, maybe add an additional type check? ( Ignorable by Annotation )

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

710
                iterator_to_array(/** @scrutinizer ignore-type */ $enum->astNode->values),
Loading history...
711
                function (EnumValueDefinitionNode $value) use ($valueName) {
712 1
                    return $value->name->value === $valueName;
713 1
                }
714
            );
715
        }
716 76
        return $enum->astNode ?
0 ignored issues
show
Bug Best Practice introduced by
The expression return $enum->astNode ? ...>astNode->values : null also could return the type GraphQL\Language\AST\NodeList which is incompatible with the documented return type GraphQL\Language\AST\EnumValueDefinitionNode[].
Loading history...
717 76
            $enum->astNode->values : null;
718
    }
719
720
    /**
721
     * @param string $message
722
     * @param array|Node|TypeNode|TypeDefinitionNode $nodes
723
     */
724 42
    private function reportError($message, $nodes = null)
725
    {
726 42
        $nodes = array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]);
727 42
        $this->addError(new Error($message, $nodes));
728 42
    }
729
730
    /**
731
     * @param Error $error
732
     */
733 47
    private function addError($error)
734
    {
735 47
        $this->errors[] = $error;
736 47
    }
737
}
738