Failed Conditions
Push — master ( 177394...bf4e7d )
by Vladimir
09:19
created

TypeInfo::getDefaultValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2.0625
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\ArgumentNode;
10
use GraphQL\Language\AST\DirectiveNode;
11
use GraphQL\Language\AST\EnumValueNode;
12
use GraphQL\Language\AST\FieldNode;
13
use GraphQL\Language\AST\FragmentDefinitionNode;
14
use GraphQL\Language\AST\InlineFragmentNode;
15
use GraphQL\Language\AST\ListTypeNode;
16
use GraphQL\Language\AST\ListValueNode;
17
use GraphQL\Language\AST\NamedTypeNode;
18
use GraphQL\Language\AST\Node;
19
use GraphQL\Language\AST\NonNullTypeNode;
20
use GraphQL\Language\AST\ObjectFieldNode;
21
use GraphQL\Language\AST\OperationDefinitionNode;
22
use GraphQL\Language\AST\SelectionSetNode;
23
use GraphQL\Language\AST\VariableDefinitionNode;
24
use GraphQL\Type\Definition\CompositeType;
25
use GraphQL\Type\Definition\Directive;
26
use GraphQL\Type\Definition\EnumType;
27
use GraphQL\Type\Definition\FieldArgument;
28
use GraphQL\Type\Definition\FieldDefinition;
29
use GraphQL\Type\Definition\InputObjectType;
30
use GraphQL\Type\Definition\InputType;
31
use GraphQL\Type\Definition\InterfaceType;
32
use GraphQL\Type\Definition\ListOfType;
33
use GraphQL\Type\Definition\NonNull;
34
use GraphQL\Type\Definition\ObjectType;
35
use GraphQL\Type\Definition\OutputType;
36
use GraphQL\Type\Definition\ScalarType;
37
use GraphQL\Type\Definition\Type;
38
use GraphQL\Type\Definition\UnionType;
39
use GraphQL\Type\Definition\WrappingType;
40
use GraphQL\Type\Introspection;
41
use GraphQL\Type\Schema;
42
use SplStack;
43
use function array_map;
44
use function array_merge;
45
use function array_pop;
46
use function count;
47
use function is_array;
48
use function sprintf;
49
50
class TypeInfo
51
{
52
    /** @var Schema */
53
    private $schema;
54
55
    /** @var SplStack<OutputType> */
56
    private $typeStack;
57
58
    /** @var SplStack<CompositeType> */
59
    private $parentTypeStack;
60
61
    /** @var SplStack<InputType> */
62
    private $inputTypeStack;
63
64
    /** @var SplStack<FieldDefinition> */
65
    private $fieldDefStack;
66
67
    /** @var SplStack<mixed> */
68
    private $defaultValueStack;
69
70
    /** @var Directive */
71
    private $directive;
72
73
    /** @var FieldArgument */
74
    private $argument;
75
76
    /** @var mixed */
77
    private $enumValue;
78
79
    /**
80
     * @param Type|null $initialType
81
     */
82 581
    public function __construct(Schema $schema, $initialType = null)
83
    {
84 581
        $this->schema            = $schema;
85 581
        $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...
86 581
        $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...
87 581
        $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...
88 581
        $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...
89 581
        $this->defaultValueStack = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type SplStack of property $defaultValueStack.

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

498
                array_pop(/** @scrutinizer ignore-type */ $this->parentTypeStack);
Loading history...
499 526
                break;
500
501 581
            case $node instanceof FieldNode:
502 516
                array_pop($this->fieldDefStack);
503 516
                array_pop($this->typeStack);
504 516
                break;
505
506 581
            case $node instanceof DirectiveNode:
507 54
                $this->directive = null;
508 54
                break;
509
510 581
            case $node instanceof OperationDefinitionNode:
511 581
            case $node instanceof InlineFragmentNode:
512 581
            case $node instanceof FragmentDefinitionNode:
513 526
                array_pop($this->typeStack);
514 526
                break;
515 581
            case $node instanceof VariableDefinitionNode:
516 82
                array_pop($this->inputTypeStack);
517 82
                break;
518 581
            case $node instanceof ArgumentNode:
519 266
                $this->argument = null;
520 266
                array_pop($this->defaultValueStack);
521 266
                array_pop($this->inputTypeStack);
522 266
                break;
523 581
            case $node instanceof ListValueNode:
524 581
            case $node instanceof ObjectFieldNode:
525 29
                array_pop($this->defaultValueStack);
526 29
                array_pop($this->inputTypeStack);
527 29
                break;
528 581
            case $node instanceof EnumValueNode:
529 31
                $this->enumValue = null;
530 31
                break;
531
        }
532 581
    }
533
}
534