Passed
Push — master ( d8d032...bdbb30 )
by Vladimir
05:55
created

TypeInfo::extractTypes()   D

Complexity

Conditions 18
Paths 82

Size

Total Lines 71
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 18

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 71
ccs 43
cts 43
cp 1
rs 4.8666
c 0
b 0
f 0
cc 18
nc 82
nop 2
crap 18

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\OutputType;
26
use GraphQL\Type\Definition\Type;
27
use GraphQL\Type\Definition\UnionType;
28
use GraphQL\Type\Definition\WrappingType;
29
use GraphQL\Type\Introspection;
30
use GraphQL\Type\Schema;
31
use function array_map;
32
use function array_merge;
33
use function array_pop;
34
use function count;
35
use function is_array;
36
use function sprintf;
37
38
/**
39
 * Class TypeInfo
40
 */
41
class TypeInfo
42
{
43
    /** @var Schema */
44
    private $schema;
45
46
    /** @var \SplStack<OutputType> */
47
    private $typeStack;
48
49
    /** @var \SplStack<CompositeType> */
50
    private $parentTypeStack;
51
52
    /** @var \SplStack<InputType> */
53
    private $inputTypeStack;
54
55
    /** @var \SplStack<FieldDefinition> */
56
    private $fieldDefStack;
57
58
    /** @var Directive */
59
    private $directive;
60
61
    /** @var FieldArgument */
62
    private $argument;
63
64
    /** @var mixed */
65
    private $enumValue;
66
67
    /**
68
     * @param Type|null $initialType
69
     */
70 573
    public function __construct(Schema $schema, $initialType = null)
71
    {
72 573
        $this->schema          = $schema;
73 573
        $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...
74 573
        $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...
75 573
        $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...
76 573
        $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...
77 573
        if (! $initialType) {
78 571
            return;
79
        }
80
81 2
        if (Type::isInputType($initialType)) {
82 2
            $this->inputTypeStack[] = $initialType;
83
        }
84 2
        if (Type::isCompositeType($initialType)) {
85
            $this->parentTypeStack[] = $initialType;
86
        }
87 2
        if (! Type::isOutputType($initialType)) {
88
            return;
89
        }
90
91 2
        $this->typeStack[] = $initialType;
92 2
    }
93
94
    /**
95
     * @deprecated moved to GraphQL\Utils\TypeComparators
96
     */
97
    public static function isEqualType(Type $typeA, Type $typeB)
98
    {
99
        return TypeComparators::isEqualType($typeA, $typeB);
100
    }
101
102
    /**
103
     * @deprecated moved to GraphQL\Utils\TypeComparators
104
     */
105
    public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
106
    {
107
        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

107
        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

107
        return TypeComparators::isTypeSubTypeOf($schema, /** @scrutinizer ignore-type */ $maybeSubType, $superType);
Loading history...
108
    }
109
110
    /**
111
     * @deprecated moved to GraphQL\Utils\TypeComparators
112
     */
113
    public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
114
    {
115
        return TypeComparators::doTypesOverlap($schema, $typeA, $typeB);
116
    }
117
118
    /**
119
     * Given root type scans through all fields to find nested types. Returns array where keys are for type name
120
     * and value contains corresponding type instance.
121
     *
122
     * Example output:
123
     * [
124
     *     'String' => $instanceOfStringType,
125
     *     'MyType' => $instanceOfMyType,
126
     *     ...
127
     * ]
128
     *
129
     * @param Type|null   $type
130
     * @param Type[]|null $typeMap
131
     *
132
     * @return Type[]|null
133
     */
134 831
    public static function extractTypes($type, ?array $typeMap = null)
135
    {
136 831
        if (! $typeMap) {
137 831
            $typeMap = [];
138
        }
139 831
        if (! $type) {
140 7
            return $typeMap;
141
        }
142
143 831
        if ($type instanceof WrappingType) {
144 823
            return self::extractTypes($type->getWrappedType(true), $typeMap);
145
        }
146 831
        if (! $type instanceof Type) {
0 ignored issues
show
introduced by
$type is always a sub-type of GraphQL\Type\Definition\Type.
Loading history...
147
            // Preserve these invalid types in map (at numeric index) to make them
148
            // detectable during $schema->validate()
149 4
            $i            = 0;
150 4
            $alreadyInMap = false;
151 4
            while (isset($typeMap[$i])) {
152 1
                $alreadyInMap = $alreadyInMap || $typeMap[$i] === $type;
153 1
                $i++;
154
            }
155 4
            if (! $alreadyInMap) {
156 4
                $typeMap[$i] = $type;
157
            }
158 4
            return $typeMap;
159
        }
160
161 831
        if (! empty($typeMap[$type->name])) {
162 826
            Utils::invariant(
163 826
                $typeMap[$type->name] === $type,
164 826
                sprintf('Schema must contain unique named types but contains multiple types named "%s" ', $type) .
165 826
                '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
166
            );
167
168 823
            return $typeMap;
169
        }
170 831
        $typeMap[$type->name] = $type;
171
172 831
        $nestedTypes = [];
173
174 831
        if ($type instanceof UnionType) {
175 440
            $nestedTypes = $type->getTypes();
176
        }
177 831
        if ($type instanceof ObjectType) {
178 831
            $nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
179
        }
180 830
        if ($type instanceof ObjectType || $type instanceof InterfaceType) {
181 830
            foreach ((array) $type->getFields() as $fieldName => $field) {
182 828
                if (! empty($field->args)) {
183 822
                    $fieldArgTypes = array_map(
184
                        static function (FieldArgument $arg) {
185 822
                            return $arg->getType();
186 822
                        },
187 822
                        $field->args
188
                    );
189
190 822
                    $nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
191
                }
192 828
                $nestedTypes[] = $field->getType();
193
            }
194
        }
195 828
        if ($type instanceof InputObjectType) {
196 472
            foreach ((array) $type->getFields() as $fieldName => $field) {
197 471
                $nestedTypes[] = $field->getType();
198
            }
199
        }
200 828
        foreach ($nestedTypes as $type) {
0 ignored issues
show
introduced by
$type is overwriting one of the parameters of this function.
Loading history...
201 828
            $typeMap = self::extractTypes($type, $typeMap);
202
        }
203
204 826
        return $typeMap;
205
    }
206
207
    /**
208
     * @param Type[] $typeMap
209
     *
210
     * @return Type[]
211
     */
212 820
    public static function extractTypesFromDirectives(Directive $directive, array $typeMap = [])
213
    {
214 820
        if (is_array($directive->args)) {
0 ignored issues
show
introduced by
The condition is_array($directive->args) is always true.
Loading history...
215 819
            foreach ($directive->args as $arg) {
216 819
                $typeMap = self::extractTypes($arg->getType(), $typeMap);
217
            }
218
        }
219
220 820
        return $typeMap;
221
    }
222
223
    /**
224
     * @return InputType|null
225
     */
226 18
    public function getParentInputType()
227
    {
228 18
        $inputTypeStackLength = count($this->inputTypeStack);
229 18
        if ($inputTypeStackLength > 1) {
230 18
            return $this->inputTypeStack[$inputTypeStackLength - 2];
231
        }
232
    }
233
234
    /**
235
     * @return FieldArgument|null
236
     */
237 62
    public function getArgument()
238
    {
239 62
        return $this->argument;
240
    }
241
242
    /**
243
     * @return mixed
244
     */
245
    public function getEnumValue()
246
    {
247
        return $this->enumValue;
248
    }
249
250 573
    public function enter(Node $node)
251
    {
252 573
        $schema = $this->schema;
253
254
        // Note: many of the types below are explicitly typed as "mixed" to drop
255
        // any assumptions of a valid schema to ensure runtime types are properly
256
        // checked before continuing since TypeInfo is used as part of validation
257
        // which occurs before guarantees of schema and document validity.
258 573
        switch ($node->kind) {
259 573
            case NodeKind::SELECTION_SET:
260 519
                $namedType               = Type::getNamedType($this->getType());
261 519
                $this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null;
262 519
                break;
263
264 573
            case NodeKind::FIELD:
265 509
                $parentType = $this->getParentType();
266 509
                $fieldDef   = null;
267 509
                if ($parentType) {
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
268 462
                    $fieldDef = self::getFieldDefinition($schema, $parentType, $node);
269
                }
270 509
                $fieldType = null;
271 509
                if ($fieldDef) {
0 ignored issues
show
introduced by
$fieldDef is of type GraphQL\Type\Definition\FieldDefinition, thus it always evaluated to true.
Loading history...
272 388
                    $fieldType = $fieldDef->getType();
273
                }
274 509
                $this->fieldDefStack[] = $fieldDef;
275 509
                $this->typeStack[]     = Type::isOutputType($fieldType) ? $fieldType : null;
276 509
                break;
277
278 573
            case NodeKind::DIRECTIVE:
279 54
                $this->directive = $schema->getDirective($node->name->value);
280 54
                break;
281
282 573
            case NodeKind::OPERATION_DEFINITION:
283 407
                $type = null;
284 407
                if ($node->operation === 'query') {
285 405
                    $type = $schema->getQueryType();
286 9
                } elseif ($node->operation === 'mutation') {
287 6
                    $type = $schema->getMutationType();
288 4
                } elseif ($node->operation === 'subscription') {
289 4
                    $type = $schema->getSubscriptionType();
290
                }
291 407
                $this->typeStack[] = Type::isOutputType($type) ? $type : null;
292 407
                break;
293
294 573
            case NodeKind::INLINE_FRAGMENT:
295 573
            case NodeKind::FRAGMENT_DEFINITION:
296 223
                $typeConditionNode = $node->typeCondition;
297 223
                $outputType        = $typeConditionNode ? self::typeFromAST(
298 221
                    $schema,
299 221
                    $typeConditionNode
300 223
                ) : Type::getNamedType($this->getType());
301 223
                $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null;
302 223
                break;
303
304 573
            case NodeKind::VARIABLE_DEFINITION:
305 85
                $inputType              = self::typeFromAST($schema, $node->type);
306 85
                $this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push
307 85
                break;
308
309 573
            case NodeKind::ARGUMENT:
310 259
                $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
311 259
                $argDef           = $argType = null;
312 259
                if ($fieldOrDirective) {
313 207
                    $argDef = Utils::find(
314 207
                        $fieldOrDirective->args,
315
                        static function ($arg) use ($node) {
316 206
                            return $arg->name === $node->name->value;
317 207
                        }
318
                    );
319 207
                    if ($argDef !== null) {
320 199
                        $argType = $argDef->getType();
321
                    }
322
                }
323 259
                $this->argument         = $argDef;
324 259
                $this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null;
325 259
                break;
326
327 573
            case NodeKind::LST:
328 10
                $listType               = Type::getNullableType($this->getInputType());
329 10
                $itemType               = $listType instanceof ListOfType
330 9
                    ? $listType->getWrappedType()
331 10
                    : $listType;
332 10
                $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
333 10
                break;
334
335 573
            case NodeKind::OBJECT_FIELD:
336 24
                $objectType     = Type::getNamedType($this->getInputType());
337 24
                $fieldType      = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldType is dead and can be removed.
Loading history...
338 24
                $inputFieldType = null;
339 24
                if ($objectType instanceof InputObjectType) {
340 16
                    $tmp            = $objectType->getFields();
341 16
                    $inputField     = $tmp[$node->name->value] ?? null;
342 16
                    $inputFieldType = $inputField ? $inputField->getType() : null;
343
                }
344 24
                $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null;
345 24
                break;
346
347 573
            case NodeKind::ENUM:
348 31
                $enumType  = Type::getNamedType($this->getInputType());
349 31
                $enumValue = null;
350 31
                if ($enumType instanceof EnumType) {
351 22
                    $enumValue = $enumType->getValue($node->value);
352
                }
353 31
                $this->enumValue = $enumValue;
354 31
                break;
355
        }
356 573
    }
357
358
    /**
359
     * @return Type
360
     */
361 519
    public function getType()
362
    {
363 519
        if (! empty($this->typeStack)) {
364 519
            return $this->typeStack[count($this->typeStack) - 1];
365
        }
366
367 2
        return null;
368
    }
369
370
    /**
371
     * @return Type
372
     */
373 509
    public function getParentType()
374
    {
375 509
        if (! empty($this->parentTypeStack)) {
376 509
            return $this->parentTypeStack[count($this->parentTypeStack) - 1];
377
        }
378
379 2
        return null;
380
    }
381
382
    /**
383
     * Not exactly the same as the executor's definition of getFieldDef, in this
384
     * statically evaluated environment we do not always have an Object type,
385
     * and need to handle Interface and Union types.
386
     *
387
     * @return FieldDefinition
388
     */
389 462
    private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode)
390
    {
391 462
        $name       = $fieldNode->name->value;
392 462
        $schemaMeta = Introspection::schemaMetaFieldDef();
393 462
        if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) {
0 ignored issues
show
introduced by
The condition $schema->getQueryType() === $parentType is always false.
Loading history...
394 8
            return $schemaMeta;
395
        }
396
397 462
        $typeMeta = Introspection::typeMetaFieldDef();
398 462
        if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) {
0 ignored issues
show
introduced by
The condition $schema->getQueryType() === $parentType is always false.
Loading history...
399 21
            return $typeMeta;
400
        }
401 462
        $typeNameMeta = Introspection::typeNameMetaFieldDef();
402 462
        if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) {
403 18
            return $typeNameMeta;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $typeNameMeta returns the type GraphQL\Type\Definition\Type which is incompatible with the documented return type GraphQL\Type\Definition\FieldDefinition.
Loading history...
404
        }
405 452
        if ($parentType instanceof ObjectType ||
406 452
            $parentType instanceof InterfaceType) {
407 450
            $fields = $parentType->getFields();
408
409 450
            return $fields[$name] ?? null;
410
        }
411
412 2
        return null;
413
    }
414
415
    /**
416
     * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
417
     *
418
     * @return Type|null
419
     *
420
     * @throws InvariantViolation
421
     */
422 333
    public static function typeFromAST(Schema $schema, $inputTypeNode)
423
    {
424 333
        return AST::typeFromAST($schema, $inputTypeNode);
425
    }
426
427
    /**
428
     * @return Directive|null
429
     */
430 261
    public function getDirective()
431
    {
432 261
        return $this->directive;
433
    }
434
435
    /**
436
     * @return FieldDefinition
437
     */
438 320
    public function getFieldDef()
439
    {
440 320
        if (! empty($this->fieldDefStack)) {
441 320
            return $this->fieldDefStack[count($this->fieldDefStack) - 1];
442
        }
443
444
        return null;
445
    }
446
447
    /**
448
     * @return InputType
449
     */
450 202
    public function getInputType()
451
    {
452 202
        if (! empty($this->inputTypeStack)) {
453 202
            return $this->inputTypeStack[count($this->inputTypeStack) - 1];
454
        }
455
456 2
        return null;
457
    }
458
459 573
    public function leave(Node $node)
460
    {
461 573
        switch ($node->kind) {
462 573
            case NodeKind::SELECTION_SET:
463 519
                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

463
                array_pop(/** @scrutinizer ignore-type */ $this->parentTypeStack);
Loading history...
464 519
                break;
465
466 573
            case NodeKind::FIELD:
467 509
                array_pop($this->fieldDefStack);
468 509
                array_pop($this->typeStack);
469 509
                break;
470
471 573
            case NodeKind::DIRECTIVE:
472 54
                $this->directive = null;
473 54
                break;
474
475 573
            case NodeKind::OPERATION_DEFINITION:
476 573
            case NodeKind::INLINE_FRAGMENT:
477 573
            case NodeKind::FRAGMENT_DEFINITION:
478 519
                array_pop($this->typeStack);
479 519
                break;
480 573
            case NodeKind::VARIABLE_DEFINITION:
481 85
                array_pop($this->inputTypeStack);
482 85
                break;
483 573
            case NodeKind::ARGUMENT:
484 259
                $this->argument = null;
485 259
                array_pop($this->inputTypeStack);
486 259
                break;
487 573
            case NodeKind::LST:
488 573
            case NodeKind::OBJECT_FIELD:
489 30
                array_pop($this->inputTypeStack);
490 30
                break;
491 573
            case NodeKind::ENUM:
492 31
                $this->enumValue = null;
493 31
                break;
494
        }
495 573
    }
496
}
497