Passed
Push — master ( 7d326c...9ae8b9 )
by Vladimir
03:29
created

TypeInfo::extractTypesFromDirectives()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
ccs 5
cts 5
cp 1
cc 3
nc 2
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use GraphQL\Error\InvariantViolation;
8
use GraphQL\Error\Warning;
9
use GraphQL\Language\AST\FieldNode;
10
use GraphQL\Language\AST\ListTypeNode;
11
use GraphQL\Language\AST\NamedTypeNode;
12
use GraphQL\Language\AST\Node;
13
use GraphQL\Language\AST\NodeKind;
14
use GraphQL\Language\AST\NonNullTypeNode;
15
use GraphQL\Type\Definition\CompositeType;
16
use GraphQL\Type\Definition\Directive;
17
use GraphQL\Type\Definition\EnumType;
18
use GraphQL\Type\Definition\FieldArgument;
19
use GraphQL\Type\Definition\FieldDefinition;
20
use GraphQL\Type\Definition\InputObjectType;
21
use GraphQL\Type\Definition\InputType;
22
use GraphQL\Type\Definition\InterfaceType;
23
use GraphQL\Type\Definition\ListOfType;
24
use GraphQL\Type\Definition\ObjectType;
25
use GraphQL\Type\Definition\Type;
26
use GraphQL\Type\Definition\UnionType;
27
use GraphQL\Type\Definition\WrappingType;
28
use GraphQL\Type\Introspection;
29
use GraphQL\Type\Schema;
30
use function array_map;
31
use function array_merge;
32
use function array_pop;
33
use function count;
34
use function sprintf;
35
36
/**
37
 * Class TypeInfo
38
 */
39
class TypeInfo
40
{
41
    /** @var Schema */
42
    private $schema;
43
44
    /** @var \SplStack<OutputType> */
45
    private $typeStack;
46
47
    /** @var \SplStack<CompositeType> */
48
    private $parentTypeStack;
49
50
    /** @var \SplStack<InputType> */
51
    private $inputTypeStack;
52
53
    /** @var \SplStack<FieldDefinition> */
54
    private $fieldDefStack;
55
56
    /** @var Directive */
57
    private $directive;
58
59
    /** @var FieldArgument */
60
    private $argument;
61
62
    /** @var mixed */
63
    private $enumValue;
64
65
    /**
66
     *
67
     * @param Type|null $initialType
68
     */
69 516
    public function __construct(Schema $schema, $initialType = null)
70
    {
71 516
        $this->schema          = $schema;
72 516
        $this->typeStack       = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type SplStack of property $typeStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
73 516
        $this->parentTypeStack = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type SplStack of property $parentTypeStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
74 516
        $this->inputTypeStack  = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type SplStack of property $inputTypeStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
75 516
        $this->fieldDefStack   = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type SplStack of property $fieldDefStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
76 516
        if (! $initialType) {
77 514
            return;
78
        }
79
80 2
        if (Type::isInputType($initialType)) {
81 2
            $this->inputTypeStack[] = $initialType;
82
        }
83 2
        if (Type::isCompositeType($initialType)) {
84
            $this->parentTypeStack[] = $initialType;
85
        }
86 2
        if (! Type::isOutputType($initialType)) {
87
            return;
88
        }
89
90 2
        $this->typeStack[] = $initialType;
91 2
    }
92
93
    /**
94
     * @deprecated moved to GraphQL\Utils\TypeComparators
95
     */
96
    public static function isEqualType(Type $typeA, Type $typeB)
97
    {
98
        return TypeComparators::isEqualType($typeA, $typeB);
99
    }
100
101
    /**
102
     * @deprecated moved to GraphQL\Utils\TypeComparators
103
     */
104
    public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
105
    {
106
        return TypeComparators::isTypeSubTypeOf($schema, $maybeSubType, $superType);
0 ignored issues
show
Bug introduced by
$superType 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

106
        return TypeComparators::isTypeSubTypeOf($schema, $maybeSubType, /** @scrutinizer ignore-type */ $superType);
Loading history...
Bug introduced by
$maybeSubType 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

106
        return TypeComparators::isTypeSubTypeOf($schema, /** @scrutinizer ignore-type */ $maybeSubType, $superType);
Loading history...
107
    }
108
109
    /**
110
     * @deprecated moved to GraphQL\Utils\TypeComparators
111
     */
112
    public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
113
    {
114
        return TypeComparators::doTypesOverlap($schema, $typeA, $typeB);
115
    }
116
117
    /**
118
     * Given root type scans through all fields to find nested types. Returns array where keys are for type name
119
     * and value contains corresponding type instance.
120
     *
121
     * Example output:
122
     * [
123
     *     'String' => $instanceOfStringType,
124
     *     'MyType' => $instanceOfMyType,
125
     *     ...
126
     * ]
127
     *
128
     * @param Type|null   $type
129
     * @param Type[]|null $typeMap
130
     * @return Type[]|null
131
     */
132 761
    public static function extractTypes($type, ?array $typeMap = null)
133
    {
134 761
        if (! $typeMap) {
135 761
            $typeMap = [];
136
        }
137 761
        if (! $type) {
138 7
            return $typeMap;
139
        }
140
141 761
        if ($type instanceof WrappingType) {
142 753
            return self::extractTypes($type->getWrappedType(true), $typeMap);
143
        }
144 761
        if (! $type instanceof Type) {
0 ignored issues
show
introduced by
$type is always a sub-type of GraphQL\Type\Definition\Type.
Loading history...
145 4
            Warning::warnOnce(
146
                'One of the schema types is not a valid type definition instance. ' .
147 4
                'Try running $schema->assertValid() to find out the cause of this warning.',
148 4
                Warning::WARNING_NOT_A_TYPE
149
            );
150
151 4
            return $typeMap;
152
        }
153
154 761
        if (! empty($typeMap[$type->name])) {
155 756
            Utils::invariant(
156 756
                $typeMap[$type->name] === $type,
157 756
                sprintf('Schema must contain unique named types but contains multiple types named "%s" ', $type) .
158 756
                '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
159
            );
160
161 753
            return $typeMap;
162
        }
163 761
        $typeMap[$type->name] = $type;
164
165 761
        $nestedTypes = [];
166
167 761
        if ($type instanceof UnionType) {
168 387
            $nestedTypes = $type->getTypes();
169
        }
170 761
        if ($type instanceof ObjectType) {
171 761
            $nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
172
        }
173 760
        if ($type instanceof ObjectType || $type instanceof InterfaceType) {
174 760
            foreach ((array) $type->getFields() as $fieldName => $field) {
175 758
                if (! empty($field->args)) {
176 752
                    $fieldArgTypes = array_map(
177
                        function (FieldArgument $arg) {
178 752
                            return $arg->getType();
179 752
                        },
180 752
                        $field->args
181
                    );
182
183 752
                    $nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
184
                }
185 758
                $nestedTypes[] = $field->getType();
186
            }
187
        }
188 758
        if ($type instanceof InputObjectType) {
189 418
            foreach ((array) $type->getFields() as $fieldName => $field) {
190 417
                $nestedTypes[] = $field->getType();
191
            }
192
        }
193 758
        foreach ($nestedTypes as $type) {
0 ignored issues
show
introduced by
$type is overwriting one of the parameters of this function.
Loading history...
194 758
            $typeMap = self::extractTypes($type, $typeMap);
195
        }
196
197 756
        return $typeMap;
198
    }
199
200
    /**
201
     * @param Directive $directive
202
     * @param array $typeMap
203
     * @return array
204
     */
205 748
    public static function extractTypesFromDirectives(Directive $directive, array $typeMap = [])
206
    {
207 748
        if (is_array($directive->args)) {
0 ignored issues
show
introduced by
The condition is_array($directive->args) is always true.
Loading history...
208 747
            foreach ($directive->args as $arg) {
209 747
                $typeMap = self::extractTypes($arg->getType(), $typeMap);
210
            }
211
        }
212 748
        return $typeMap;
213
    }
214
215
    /**
216
     * @return InputType|null
217
     */
218 18
    public function getParentInputType()
219
    {
220 18
        $inputTypeStackLength = count($this->inputTypeStack);
221 18
        if ($inputTypeStackLength > 1) {
222 18
            return $this->inputTypeStack[$inputTypeStackLength - 2];
223
        }
224
    }
225
226
    /**
227
     * @return FieldArgument|null
228
     */
229 62
    public function getArgument()
230
    {
231 62
        return $this->argument;
232
    }
233
234
    /**
235
     * @return mixed
236
     */
237
    public function getEnumValue()
238
    {
239
        return $this->enumValue;
240
    }
241
242 516
    public function enter(Node $node)
243
    {
244 516
        $schema = $this->schema;
245
246
        // Note: many of the types below are explicitly typed as "mixed" to drop
247
        // any assumptions of a valid schema to ensure runtime types are properly
248
        // checked before continuing since TypeInfo is used as part of validation
249
        // which occurs before guarantees of schema and document validity.
250 516
        switch ($node->kind) {
251 516
            case NodeKind::SELECTION_SET:
252 512
                $namedType               = Type::getNamedType($this->getType());
253 512
                $this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null;
254 512
                break;
255
256 516
            case NodeKind::FIELD:
257 502
                $parentType = $this->getParentType();
258 502
                $fieldDef   = null;
259 502
                if ($parentType) {
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
260 455
                    $fieldDef = self::getFieldDefinition($schema, $parentType, $node);
261
                }
262 502
                $fieldType = null;
263 502
                if ($fieldDef) {
0 ignored issues
show
introduced by
$fieldDef is of type GraphQL\Type\Definition\FieldDefinition, thus it always evaluated to true.
Loading history...
264 383
                    $fieldType = $fieldDef->getType();
265
                }
266 502
                $this->fieldDefStack[] = $fieldDef;
267 502
                $this->typeStack[]     = Type::isOutputType($fieldType) ? $fieldType : null;
268 502
                break;
269
270 516
            case NodeKind::DIRECTIVE:
271 44
                $this->directive = $schema->getDirective($node->name->value);
272 44
                break;
273
274 516
            case NodeKind::OPERATION_DEFINITION:
275 401
                $type = null;
276 401
                if ($node->operation === 'query') {
277 399
                    $type = $schema->getQueryType();
278 9
                } elseif ($node->operation === 'mutation') {
279 6
                    $type = $schema->getMutationType();
280 4
                } elseif ($node->operation === 'subscription') {
281 4
                    $type = $schema->getSubscriptionType();
282
                }
283 401
                $this->typeStack[] = Type::isOutputType($type) ? $type : null;
284 401
                break;
285
286 516
            case NodeKind::INLINE_FRAGMENT:
287 516
            case NodeKind::FRAGMENT_DEFINITION:
288 222
                $typeConditionNode = $node->typeCondition;
289 222
                $outputType        = $typeConditionNode ? self::typeFromAST(
290 220
                    $schema,
291 220
                    $typeConditionNode
292 222
                ) : Type::getNamedType($this->getType());
293 222
                $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null;
294 222
                break;
295
296 516
            case NodeKind::VARIABLE_DEFINITION:
297 84
                $inputType              = self::typeFromAST($schema, $node->type);
298 84
                $this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push
299 84
                break;
300
301 516
            case NodeKind::ARGUMENT:
302 255
                $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
303 255
                $argDef           = $argType = null;
304 255
                if ($fieldOrDirective) {
305 203
                    $argDef = Utils::find(
306 203
                        $fieldOrDirective->args,
307
                        function ($arg) use ($node) {
308 202
                            return $arg->name === $node->name->value;
309 203
                        }
310
                    );
311 203
                    if ($argDef) {
312 195
                        $argType = $argDef->getType();
313
                    }
314
                }
315 255
                $this->argument         = $argDef;
316 255
                $this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null;
317 255
                break;
318
319 516
            case NodeKind::LST:
320 10
                $listType               = Type::getNullableType($this->getInputType());
321 10
                $itemType               = $listType instanceof ListOfType
322 9
                    ? $listType->getWrappedType()
323 10
                    : $listType;
324 10
                $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
325 10
                break;
326
327 516
            case NodeKind::OBJECT_FIELD:
328 24
                $objectType     = Type::getNamedType($this->getInputType());
329 24
                $fieldType      = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldType is dead and can be removed.
Loading history...
330 24
                $inputFieldType = null;
331 24
                if ($objectType instanceof InputObjectType) {
332 16
                    $tmp            = $objectType->getFields();
333 16
                    $inputField     = $tmp[$node->name->value] ?? null;
334 16
                    $inputFieldType = $inputField ? $inputField->getType() : null;
335
                }
336 24
                $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null;
0 ignored issues
show
Bug introduced by
It seems like $inputFieldType 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

336
                $this->inputTypeStack[] = Type::isInputType(/** @scrutinizer ignore-type */ $inputFieldType) ? $inputFieldType : null;
Loading history...
337 24
                break;
338
339 516
            case NodeKind::ENUM:
340 31
                $enumType  = Type::getNamedType($this->getInputType());
341 31
                $enumValue = null;
342 31
                if ($enumType instanceof EnumType) {
343 22
                    $enumValue = $enumType->getValue($node->value);
344
                }
345 31
                $this->enumValue = $enumValue;
346 31
                break;
347
        }
348 516
    }
349
350
    /**
351
     * @return Type
352
     */
353 512
    public function getType()
354
    {
355 512
        if (! empty($this->typeStack)) {
356 512
            return $this->typeStack[count($this->typeStack) - 1];
357
        }
358
359 2
        return null;
360
    }
361
362
    /**
363
     * @return Type
364
     */
365 502
    public function getParentType()
366
    {
367 502
        if (! empty($this->parentTypeStack)) {
368 502
            return $this->parentTypeStack[count($this->parentTypeStack) - 1];
369
        }
370
371 2
        return null;
372
    }
373
374
    /**
375
     * Not exactly the same as the executor's definition of getFieldDef, in this
376
     * statically evaluated environment we do not always have an Object type,
377
     * and need to handle Interface and Union types.
378
     *
379
     * @return FieldDefinition
380
     */
381 455
    private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode)
382
    {
383 455
        $name       = $fieldNode->name->value;
384 455
        $schemaMeta = Introspection::schemaMetaFieldDef();
385 455
        if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) {
0 ignored issues
show
introduced by
The condition $schema->getQueryType() === $parentType is always false.
Loading history...
386 8
            return $schemaMeta;
387
        }
388
389 455
        $typeMeta = Introspection::typeMetaFieldDef();
390 455
        if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) {
0 ignored issues
show
introduced by
The condition $schema->getQueryType() === $parentType is always false.
Loading history...
391 21
            return $typeMeta;
392
        }
393 455
        $typeNameMeta = Introspection::typeNameMetaFieldDef();
394 455
        if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) {
395 18
            return $typeNameMeta;
396
        }
397 445
        if ($parentType instanceof ObjectType ||
398 445
            $parentType instanceof InterfaceType) {
399 443
            $fields = $parentType->getFields();
400
401 443
            return $fields[$name] ?? null;
402
        }
403
404 2
        return null;
405
    }
406
407
    /**
408
     * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
409
     * @return Type|null
410
     * @throws InvariantViolation
411
     */
412 331
    public static function typeFromAST(Schema $schema, $inputTypeNode)
413
    {
414 331
        return AST::typeFromAST($schema, $inputTypeNode);
415
    }
416
417
    /**
418
     * @return Directive|null
419
     */
420 257
    public function getDirective()
421
    {
422 257
        return $this->directive;
423
    }
424
425
    /**
426
     * @return FieldDefinition
427
     */
428 314
    public function getFieldDef()
429
    {
430 314
        if (! empty($this->fieldDefStack)) {
431 314
            return $this->fieldDefStack[count($this->fieldDefStack) - 1];
432
        }
433
434
        return null;
435
    }
436
437
    /**
438
     * @return InputType
439
     */
440 201
    public function getInputType()
441
    {
442 201
        if (! empty($this->inputTypeStack)) {
443 201
            return $this->inputTypeStack[count($this->inputTypeStack) - 1];
444
        }
445
446 2
        return null;
447
    }
448
449 516
    public function leave(Node $node)
450
    {
451 516
        switch ($node->kind) {
452 516
            case NodeKind::SELECTION_SET:
453 512
                array_pop($this->parentTypeStack);
0 ignored issues
show
Bug introduced by
$this->parentTypeStack of type SplStack is incompatible with the type array expected by parameter $array of array_pop(). ( Ignorable by Annotation )

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

453
                array_pop(/** @scrutinizer ignore-type */ $this->parentTypeStack);
Loading history...
454 512
                break;
455
456 516
            case NodeKind::FIELD:
457 502
                array_pop($this->fieldDefStack);
458 502
                array_pop($this->typeStack);
459 502
                break;
460
461 516
            case NodeKind::DIRECTIVE:
462 44
                $this->directive = null;
463 44
                break;
464
465 516
            case NodeKind::OPERATION_DEFINITION:
466 516
            case NodeKind::INLINE_FRAGMENT:
467 516
            case NodeKind::FRAGMENT_DEFINITION:
468 512
                array_pop($this->typeStack);
469 512
                break;
470 516
            case NodeKind::VARIABLE_DEFINITION:
471 84
                array_pop($this->inputTypeStack);
472 84
                break;
473 516
            case NodeKind::ARGUMENT:
474 255
                $this->argument = null;
475 255
                array_pop($this->inputTypeStack);
476 255
                break;
477 516
            case NodeKind::LST:
478 516
            case NodeKind::OBJECT_FIELD:
479 30
                array_pop($this->inputTypeStack);
480 30
                break;
481 516
            case NodeKind::ENUM:
482 31
                $this->enumValue = null;
483 31
                break;
484
        }
485 516
    }
486
}
487