TypeInfo   F
last analyzed

Complexity

Total Complexity 98

Size/Duplication

Total Lines 449
Duplicated Lines 0 %

Test Coverage

Coverage 94.55%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 98
eloc 216
dl 0
loc 449
ccs 208
cts 220
cp 0.9455
rs 2
c 1
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getDirective() 0 3 1
A getEnumValue() 0 3 1
A getArgument() 0 3 1
A getDefaultValue() 0 3 1
A getInputType() 0 3 1
A typeFromAST() 0 3 1
C leave() 0 37 12
A getFieldDef() 0 3 1
D extractTypes() 0 72 18
A extractTypesFromDirectives() 0 9 3
A __construct() 0 24 5
B getFieldDefinition() 0 24 9
A getParentInputType() 0 3 1
A isEqualType() 0 3 1
A isTypeSubTypeOf() 0 3 1
A getParentType() 0 3 1
D enter() 0 114 38
A getType() 0 3 1
A doTypesOverlap() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TypeInfo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TypeInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use GraphQL\Error\InvariantViolation;
8
use GraphQL\Language\AST\ArgumentNode;
9
use GraphQL\Language\AST\DirectiveNode;
10
use GraphQL\Language\AST\EnumValueNode;
11
use GraphQL\Language\AST\FieldNode;
12
use GraphQL\Language\AST\FragmentDefinitionNode;
13
use GraphQL\Language\AST\InlineFragmentNode;
14
use GraphQL\Language\AST\ListTypeNode;
15
use GraphQL\Language\AST\ListValueNode;
16
use GraphQL\Language\AST\NamedTypeNode;
17
use GraphQL\Language\AST\Node;
18
use GraphQL\Language\AST\NonNullTypeNode;
19
use GraphQL\Language\AST\ObjectFieldNode;
20
use GraphQL\Language\AST\OperationDefinitionNode;
21
use GraphQL\Language\AST\SelectionSetNode;
22
use GraphQL\Language\AST\VariableDefinitionNode;
23
use GraphQL\Type\Definition\CompositeType;
24
use GraphQL\Type\Definition\Directive;
25
use GraphQL\Type\Definition\EnumType;
26
use GraphQL\Type\Definition\FieldArgument;
27
use GraphQL\Type\Definition\FieldDefinition;
28
use GraphQL\Type\Definition\InputObjectType;
29
use GraphQL\Type\Definition\InputType;
30
use GraphQL\Type\Definition\InterfaceType;
31
use GraphQL\Type\Definition\ListOfType;
32
use GraphQL\Type\Definition\ObjectType;
33
use GraphQL\Type\Definition\OutputType;
34
use GraphQL\Type\Definition\Type;
35
use GraphQL\Type\Definition\UnionType;
36
use GraphQL\Type\Definition\WrappingType;
37
use GraphQL\Type\Introspection;
38
use GraphQL\Type\Schema;
39
use function array_map;
40
use function array_merge;
41
use function array_pop;
42
use function count;
43
use function is_array;
44
use function sprintf;
45
46
class TypeInfo
47
{
48
    /** @var Schema */
49
    private $schema;
50
51
    /** @var array<(OutputType&Type)|null> */
52
    private $typeStack;
53
54
    /** @var array<(CompositeType&Type)|null> */
55
    private $parentTypeStack;
56
57
    /** @var array<(InputType&Type)|null> */
58
    private $inputTypeStack;
59
60
    /** @var array<FieldDefinition> */
61
    private $fieldDefStack;
62
63
    /** @var array<mixed> */
64
    private $defaultValueStack;
65
66
    /** @var Directive|null */
67
    private $directive;
68
69
    /** @var FieldArgument|null */
70
    private $argument;
71
72
    /** @var mixed */
73
    private $enumValue;
74
75
    /**
76
     * @param Type|null $initialType
77
     */
78 535
    public function __construct(Schema $schema, $initialType = null)
79
    {
80 535
        $this->schema            = $schema;
81 535
        $this->typeStack         = [];
82 535
        $this->parentTypeStack   = [];
83 535
        $this->inputTypeStack    = [];
84 535
        $this->fieldDefStack     = [];
85 535
        $this->defaultValueStack = [];
86
87 535
        if ($initialType === null) {
88 533
            return;
89
        }
90
91 2
        if (Type::isInputType($initialType)) {
92 2
            $this->inputTypeStack[] = $initialType;
93
        }
94 2
        if (Type::isCompositeType($initialType)) {
95
            $this->parentTypeStack[] = $initialType;
96
        }
97 2
        if (! Type::isOutputType($initialType)) {
98
            return;
99
        }
100
101 2
        $this->typeStack[] = $initialType;
102 2
    }
103
104
    /**
105
     * @deprecated moved to GraphQL\Utils\TypeComparators
106
     *
107
     * @codeCoverageIgnore
108
     */
109
    public static function isEqualType(Type $typeA, Type $typeB) : bool
110
    {
111
        return TypeComparators::isEqualType($typeA, $typeB);
112
    }
113
114
    /**
115
     * @deprecated moved to GraphQL\Utils\TypeComparators
116
     *
117
     * @codeCoverageIgnore
118
     */
119
    public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
120
    {
121
        return TypeComparators::isTypeSubTypeOf($schema, $maybeSubType, $superType);
122
    }
123
124
    /**
125
     * @deprecated moved to GraphQL\Utils\TypeComparators
126
     *
127
     * @codeCoverageIgnore
128
     */
129
    public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
130
    {
131
        return TypeComparators::doTypesOverlap($schema, $typeA, $typeB);
132
    }
133
134
    /**
135
     * Given root type scans through all fields to find nested types. Returns array where keys are for type name
136
     * and value contains corresponding type instance.
137
     *
138
     * Example output:
139
     * [
140
     *     'String' => $instanceOfStringType,
141
     *     'MyType' => $instanceOfMyType,
142
     *     ...
143
     * ]
144
     *
145
     * @param Type|null   $type
146
     * @param Type[]|null $typeMap
147
     *
148
     * @return Type[]|null
149
     */
150 857
    public static function extractTypes($type, ?array $typeMap = null)
151
    {
152 857
        if (! $typeMap) {
153 857
            $typeMap = [];
154
        }
155 857
        if (! $type) {
156 1
            return $typeMap;
157
        }
158
159 857
        if ($type instanceof WrappingType) {
160 849
            return self::extractTypes($type->getWrappedType(true), $typeMap);
161
        }
162 857
        if (! $type instanceof Type) {
0 ignored issues
show
introduced by
$type is always a sub-type of GraphQL\Type\Definition\Type.
Loading history...
163
            // Preserve these invalid types in map (at numeric index) to make them
164
            // detectable during $schema->validate()
165
            $i            = 0;
166
            $alreadyInMap = false;
167
            while (isset($typeMap[$i])) {
168
                $alreadyInMap = $alreadyInMap || $typeMap[$i] === $type;
169
                $i++;
170
            }
171
            if (! $alreadyInMap) {
172
                $typeMap[$i] = $type;
173
            }
174
175
            return $typeMap;
176
        }
177
178 857
        if (! empty($typeMap[$type->name])) {
179 852
            Utils::invariant(
180 852
                $typeMap[$type->name] === $type,
181 852
                sprintf('Schema must contain unique named types but contains multiple types named "%s" ', $type) .
182 852
                '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
183
            );
184
185 849
            return $typeMap;
186
        }
187 857
        $typeMap[$type->name] = $type;
188
189 857
        $nestedTypes = [];
190
191 857
        if ($type instanceof UnionType) {
192 446
            $nestedTypes = $type->getTypes();
193
        }
194 857
        if ($type instanceof ObjectType) {
195 857
            $nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
196
        }
197 856
        if ($type instanceof ObjectType || $type instanceof InterfaceType) {
198 856
            foreach ($type->getFields() as $fieldName => $field) {
199 855
                if (! empty($field->args)) {
200 848
                    $fieldArgTypes = array_map(
201
                        static function (FieldArgument $arg) {
202 848
                            return $arg->getType();
203 848
                        },
204 848
                        $field->args
205
                    );
206
207 848
                    $nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
208
                }
209 855
                $nestedTypes[] = $field->getType();
210
            }
211
        }
212 855
        if ($type instanceof InputObjectType) {
213 481
            foreach ($type->getFields() as $fieldName => $field) {
214 480
                $nestedTypes[] = $field->getType();
215
            }
216
        }
217 855
        foreach ($nestedTypes as $nestedType) {
218 855
            $typeMap = self::extractTypes($nestedType, $typeMap);
219
        }
220
221 852
        return $typeMap;
222
    }
223
224
    /**
225
     * @param Type[] $typeMap
226
     *
227
     * @return Type[]
228
     */
229 846
    public static function extractTypesFromDirectives(Directive $directive, array $typeMap = [])
230
    {
231 846
        if (is_array($directive->args)) {
0 ignored issues
show
introduced by
The condition is_array($directive->args) is always true.
Loading history...
232 846
            foreach ($directive->args as $arg) {
233 845
                $typeMap = self::extractTypes($arg->getType(), $typeMap);
234
            }
235
        }
236
237 846
        return $typeMap;
238
    }
239
240 19
    public function getParentInputType() : ?InputType
241
    {
242 19
        return $this->inputTypeStack[count($this->inputTypeStack) - 2] ?? null;
243
    }
244
245 107
    public function getArgument() : ?FieldArgument
246
    {
247 107
        return $this->argument;
248
    }
249
250
    /**
251
     * @return mixed
252
     */
253
    public function getEnumValue()
254
    {
255
        return $this->enumValue;
256
    }
257
258 535
    public function enter(Node $node)
259
    {
260 535
        $schema = $this->schema;
261
262
        // Note: many of the types below are explicitly typed as "mixed" to drop
263
        // any assumptions of a valid schema to ensure runtime types are properly
264
        // checked before continuing since TypeInfo is used as part of validation
265
        // which occurs before guarantees of schema and document validity.
266
        switch (true) {
267 535
            case $node instanceof SelectionSetNode:
268 533
                $namedType               = Type::getNamedType($this->getType());
269 533
                $this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null;
270 533
                break;
271
272 535
            case $node instanceof FieldNode:
273 523
                $parentType = $this->getParentType();
274 523
                $fieldDef   = null;
275 523
                if ($parentType) {
276 476
                    $fieldDef = self::getFieldDefinition($schema, $parentType, $node);
277
                }
278 523
                $fieldType = null;
279 523
                if ($fieldDef) {
280 401
                    $fieldType = $fieldDef->getType();
281
                }
282 523
                $this->fieldDefStack[] = $fieldDef;
283 523
                $this->typeStack[]     = Type::isOutputType($fieldType) ? $fieldType : null;
284 523
                break;
285
286 535
            case $node instanceof DirectiveNode:
287 46
                $this->directive = $schema->getDirective($node->name->value);
288 46
                break;
289
290 535
            case $node instanceof OperationDefinitionNode:
291 421
                $type = null;
292 421
                if ($node->operation === 'query') {
293 419
                    $type = $schema->getQueryType();
294 9
                } elseif ($node->operation === 'mutation') {
295 6
                    $type = $schema->getMutationType();
296 4
                } elseif ($node->operation === 'subscription') {
297 4
                    $type = $schema->getSubscriptionType();
298
                }
299 421
                $this->typeStack[] = Type::isOutputType($type) ? $type : null;
300 421
                break;
301
302 535
            case $node instanceof InlineFragmentNode:
303 535
            case $node instanceof FragmentDefinitionNode:
304 231
                $typeConditionNode = $node->typeCondition;
305 231
                $outputType        = $typeConditionNode
306 229
                    ? self::typeFromAST(
307 229
                        $schema,
308 229
                        $typeConditionNode
309
                    )
310 231
                    : Type::getNamedType($this->getType());
311 231
                $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null;
312 231
                break;
313
314 535
            case $node instanceof VariableDefinitionNode:
315 87
                $inputType              = self::typeFromAST($schema, $node->type);
316 87
                $this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push
317 87
                break;
318
319 535
            case $node instanceof ArgumentNode:
320 267
                $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
321 267
                $argDef           = $argType = null;
322 267
                if ($fieldOrDirective) {
323
                    /** @var FieldArgument $argDef */
324 215
                    $argDef = Utils::find(
325 215
                        $fieldOrDirective->args,
326
                        static function ($arg) use ($node) {
327 214
                            return $arg->name === $node->name->value;
328 215
                        }
329
                    );
330 215
                    if ($argDef !== null) {
331 206
                        $argType = $argDef->getType();
332
                    }
333
                }
334 267
                $this->argument            = $argDef;
335 267
                $this->defaultValueStack[] = $argDef && $argDef->defaultValueExists() ? $argDef->defaultValue : Utils::undefined();
336 267
                $this->inputTypeStack[]    = Type::isInputType($argType) ? $argType : null;
337 267
                break;
338
339 535
            case $node instanceof ListValueNode:
340 10
                $type     = $this->getInputType();
341 10
                $listType = $type === null ? null : Type::getNullableType($type);
342 10
                $itemType = $listType instanceof ListOfType
343 9
                    ? $listType->getWrappedType()
344 10
                    : $listType;
345
                // List positions never have a default value.
346 10
                $this->defaultValueStack[] = Utils::undefined();
347 10
                $this->inputTypeStack[]    = Type::isInputType($itemType) ? $itemType : null;
348 10
                break;
349
350 535
            case $node instanceof ObjectFieldNode:
351 23
                $objectType     = Type::getNamedType($this->getInputType());
352 23
                $fieldType      = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldType is dead and can be removed.
Loading history...
353 23
                $inputField     = null;
354 23
                $inputFieldType = null;
355 23
                if ($objectType instanceof InputObjectType) {
356 15
                    $tmp            = $objectType->getFields();
357 15
                    $inputField     = $tmp[$node->name->value] ?? null;
358 15
                    $inputFieldType = $inputField ? $inputField->getType() : null;
359
                }
360 23
                $this->defaultValueStack[] = $inputField && $inputField->defaultValueExists() ? $inputField->defaultValue : Utils::undefined();
361 23
                $this->inputTypeStack[]    = Type::isInputType($inputFieldType) ? $inputFieldType : null;
362 23
                break;
363
364 535
            case $node instanceof EnumValueNode:
365 32
                $enumType  = Type::getNamedType($this->getInputType());
366 32
                $enumValue = null;
367 32
                if ($enumType instanceof EnumType) {
368 23
                    $this->enumValue = $enumType->getValue($node->value);
369
                }
370 32
                $this->enumValue = $enumValue;
371 32
                break;
372
        }
373 535
    }
374
375
    /**
376
     * @return (Type & OutputType) | null
0 ignored issues
show
Documentation Bug introduced by
The doc comment (Type at position 1 could not be parsed: Expected ')' at position 1, but found 'Type'.
Loading history...
377
     */
378 533
    public function getType() : ?OutputType
379
    {
380 533
        return $this->typeStack[count($this->typeStack) - 1] ?? null;
381
    }
382
383
    /**
384
     * @return (CompositeType & Type) | null
0 ignored issues
show
Documentation Bug introduced by
The doc comment (CompositeType at position 1 could not be parsed: Expected ')' at position 1, but found 'CompositeType'.
Loading history...
385
     */
386 523
    public function getParentType() : ?CompositeType
387
    {
388 523
        return $this->parentTypeStack[count($this->parentTypeStack) - 1] ?? null;
389
    }
390
391
    /**
392
     * Not exactly the same as the executor's definition of getFieldDef, in this
393
     * statically evaluated environment we do not always have an Object type,
394
     * and need to handle Interface and Union types.
395
     */
396 476
    private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode) : ?FieldDefinition
397
    {
398 476
        $name       = $fieldNode->name->value;
399 476
        $schemaMeta = Introspection::schemaMetaFieldDef();
400 476
        if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) {
0 ignored issues
show
introduced by
The condition $schema->getQueryType() === $parentType is always false.
Loading history...
401 9
            return $schemaMeta;
402
        }
403
404 476
        $typeMeta = Introspection::typeMetaFieldDef();
405 476
        if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) {
0 ignored issues
show
introduced by
The condition $schema->getQueryType() === $parentType is always false.
Loading history...
406 21
            return $typeMeta;
407
        }
408 476
        $typeNameMeta = Introspection::typeNameMetaFieldDef();
409 476
        if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) {
410 18
            return $typeNameMeta;
411
        }
412 466
        if ($parentType instanceof ObjectType ||
413 466
            $parentType instanceof InterfaceType) {
414 464
            $fields = $parentType->getFields();
415
416 464
            return $fields[$name] ?? null;
417
        }
418
419 2
        return null;
420
    }
421
422
    /**
423
     * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
424
     *
425
     * @throws InvariantViolation
426
     */
427 349
    public static function typeFromAST(Schema $schema, $inputTypeNode) : ?Type
428
    {
429 349
        return AST::typeFromAST($schema, $inputTypeNode);
430
    }
431
432 269
    public function getDirective() : ?Directive
433
    {
434 269
        return $this->directive;
435
    }
436
437 337
    public function getFieldDef() : ?FieldDefinition
438
    {
439 337
        return $this->fieldDefStack[count($this->fieldDefStack) - 1] ?? null;
440
    }
441
442
    /**
443
     * @return mixed|null
444
     */
445 64
    public function getDefaultValue()
446
    {
447 64
        return $this->defaultValueStack[count($this->defaultValueStack) - 1] ?? null;
448
    }
449
450
    /**
451
     * @return (Type & InputType) | null
0 ignored issues
show
Documentation Bug introduced by
The doc comment (Type at position 1 could not be parsed: Expected ')' at position 1, but found 'Type'.
Loading history...
452
     */
453 205
    public function getInputType() : ?InputType
454
    {
455 205
        return $this->inputTypeStack[count($this->inputTypeStack) - 1] ?? null;
456
    }
457
458 535
    public function leave(Node $node)
459
    {
460
        switch (true) {
461 535
            case $node instanceof SelectionSetNode:
462 533
                array_pop($this->parentTypeStack);
463 533
                break;
464
465 535
            case $node instanceof FieldNode:
466 523
                array_pop($this->fieldDefStack);
467 523
                array_pop($this->typeStack);
468 523
                break;
469
470 535
            case $node instanceof DirectiveNode:
471 46
                $this->directive = null;
472 46
                break;
473
474 535
            case $node instanceof OperationDefinitionNode:
475 535
            case $node instanceof InlineFragmentNode:
476 535
            case $node instanceof FragmentDefinitionNode:
477 533
                array_pop($this->typeStack);
478 533
                break;
479 535
            case $node instanceof VariableDefinitionNode:
480 87
                array_pop($this->inputTypeStack);
481 87
                break;
482 535
            case $node instanceof ArgumentNode:
483 267
                $this->argument = null;
484 267
                array_pop($this->defaultValueStack);
485 267
                array_pop($this->inputTypeStack);
486 267
                break;
487 535
            case $node instanceof ListValueNode:
488 535
            case $node instanceof ObjectFieldNode:
489 29
                array_pop($this->defaultValueStack);
490 29
                array_pop($this->inputTypeStack);
491 29
                break;
492 535
            case $node instanceof EnumValueNode:
493 32
                $this->enumValue = null;
494 32
                break;
495
        }
496 535
    }
497
}
498