Passed
Push — master ( bea54a...57c967 )
by Quang
02:18
created

ExecutionStrategy::doesFragmentConditionMatch()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
cc 5
eloc 9
nc 4
nop 2
1
<?php
2
3
namespace Digia\GraphQL\Execution;
4
5
use Digia\GraphQL\Error\ExecutionException;
6
use Digia\GraphQL\Error\InvalidTypeException;
7
use Digia\GraphQL\Execution\Resolver\ResolveInfo;
8
use Digia\GraphQL\Language\Node\FieldNode;
9
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
10
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
11
use Digia\GraphQL\Language\Node\InlineFragmentNode;
12
use Digia\GraphQL\Language\Node\NodeInterface;
13
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
14
use Digia\GraphQL\Language\Node\SelectionSetNode;
15
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
16
use Digia\GraphQL\Type\Definition\Field;
17
use Digia\GraphQL\Type\Definition\InterfaceType;
18
use Digia\GraphQL\Type\Definition\LeafTypeInterface;
19
use Digia\GraphQL\Type\Definition\ListType;
20
use Digia\GraphQL\Type\Definition\NonNullType;
21
use Digia\GraphQL\Type\Definition\ObjectType;
22
use Digia\GraphQL\Type\Definition\TypeInterface;
23
use Digia\GraphQL\Type\Definition\UnionType;
24
use Digia\GraphQL\Type\Schema;
25
use function Digia\GraphQL\Type\SchemaMetaFieldDefinition;
26
use function Digia\GraphQL\Type\TypeMetaFieldDefinition;
27
use function Digia\GraphQL\Type\TypeNameMetaFieldDefinition;
28
use function Digia\GraphQL\Util\toString;
29
use function Digia\GraphQL\Util\typeFromAST;
30
31
/**
32
 * Class AbstractStrategy
33
 * @package Digia\GraphQL\Execution\Strategies
34
 */
35
abstract class ExecutionStrategy
36
{
37
    /**
38
     * @var ExecutionContext
39
     */
40
    protected $context;
41
42
    /**
43
     * @var OperationDefinitionNode
44
     */
45
    protected $operation;
46
47
    /**
48
     * @var mixed
49
     */
50
    protected $rootValue;
51
52
53
    /**
54
     * @var ValuesResolver
55
     */
56
    protected $valuesResolver;
57
58
    /**
59
     * @var array
60
     */
61
    protected $finalResult;
62
63
    /**
64
     * @var array
65
     */
66
    protected static $defaultFieldResolver = [__CLASS__, 'defaultFieldResolver'];
67
68
    /**
69
     * AbstractStrategy constructor.
70
     * @param ExecutionContext        $context
71
     *
72
     * @param OperationDefinitionNode $operation
73
     */
74
    public function __construct(
75
        ExecutionContext $context,
76
        OperationDefinitionNode $operation,
77
        $rootValue
78
    ) {
79
        $this->context        = $context;
80
        $this->operation      = $operation;
81
        $this->rootValue      = $rootValue;
82
        $this->valuesResolver = new ValuesResolver();
83
    }
84
85
    /**
86
     * @return array|null
87
     */
88
    abstract function execute(): ?array;
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
89
90
    /**
91
     * @param ObjectType       $runtimeType
92
     * @param SelectionSetNode $selectionSet
93
     * @param                  $fields
94
     * @param                  $visitedFragmentNames
95
     * @return mixed
96
     * @throws InvalidTypeException
97
     * @throws \Digia\GraphQL\Error\ExecutionException
98
     * @throws \Digia\GraphQL\Error\InvariantException
99
     */
100
    protected function collectFields(
101
        ObjectType $runtimeType,
102
        SelectionSetNode $selectionSet,
103
        &$fields,
104
        &$visitedFragmentNames
105
    ) {
106
        foreach ($selectionSet->getSelections() as $selection) {
107
            // Check if this Node should be included first
108
            if (!$this->shouldIncludeNode($selection)) {
109
                continue;
110
            }
111
            // Collect fields
112
            if ($selection instanceof FieldNode) {
113
                $fieldName = $this->getFieldNameKey($selection);
114
                if (!isset($runtimeType->getFields()[$selection->getNameValue()])) {
115
                    continue;
116
                }
117
118
                if (!isset($fields[$fieldName])) {
119
                    $fields[$fieldName] = [];
120
                }
121
122
                $fields[$fieldName][] = $selection;
123
            } elseif ($selection instanceof InlineFragmentNode) {
124
                if (!$this->doesFragmentConditionMatch($selection, $runtimeType)) {
125
                    continue;
126
                }
127
128
                $this->collectFields($runtimeType, $selection->getSelectionSet(), $fields, $visitedFragmentNames);
129
            } elseif ($selection instanceof FragmentSpreadNode) {
130
                $fragmentName = $selection->getNameValue();
131
132
                if (!empty($visitedFragmentNames[$fragmentName])) {
133
                    continue;
134
                }
135
136
                $visitedFragmentNames[$fragmentName] = true;
137
                /** @var FragmentDefinitionNode $fragment */
138
                $fragment = $this->context->getFragments()[$fragmentName];
139
                $this->collectFields($runtimeType, $fragment->getSelectionSet(), $fields, $visitedFragmentNames);
140
            }
141
        }
142
143
        return $fields;
144
    }
145
146
147
    /**
148
     * @param $node
149
     * @return bool
150
     * @throws InvalidTypeException
151
     * @throws \Digia\GraphQL\Error\ExecutionException
152
     * @throws \Digia\GraphQL\Error\InvariantException
153
     */
154
    private function shouldIncludeNode(NodeInterface $node): bool
155
    {
156
157
        $contextVariables = $this->context->getVariableValues();
158
159
        $skip = $this->valuesResolver->getDirectiveValues(GraphQLSkipDirective(), $node, $contextVariables);
160
161
        if ($skip && $skip['if'] === true) {
162
            return false;
163
        }
164
165
        $include = $this->valuesResolver->getDirectiveValues(GraphQLIncludeDirective(), $node, $contextVariables);
166
167
        if ($include && $include['if'] === false) {
168
            return false;
169
        }
170
171
        return true;
172
    }
173
174
    /**
175
     * @param FragmentDefinitionNode|InlineFragmentNode $fragment
176
     * @param ObjectType                                $type
177
     * @return bool
178
     * @throws InvalidTypeException
179
     */
180
    private function doesFragmentConditionMatch(
181
        NodeInterface $fragment,
182
        ObjectType $type
183
    ): bool {
184
        $typeConditionNode = $fragment->getTypeCondition();
0 ignored issues
show
Bug introduced by
The method getTypeCondition() does not exist on Digia\GraphQL\Language\Node\NodeInterface. It seems like you code against a sub-type of Digia\GraphQL\Language\Node\NodeInterface such as Digia\GraphQL\Language\Node\InlineFragmentNode or Digia\GraphQL\Language\Node\FragmentDefinitionNode. ( Ignorable by Annotation )

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

184
        /** @scrutinizer ignore-call */ 
185
        $typeConditionNode = $fragment->getTypeCondition();
Loading history...
185
186
        if (!$typeConditionNode) {
187
            return true;
188
        }
189
190
        $conditionalType = typeFromAST($this->context->getSchema(), $typeConditionNode);
191
192
        // @TODO Find a better way to compare two objects
193
        // $conditionalType === $type doesn't work
194
        if (get_class($conditionalType) === get_class($type) && $conditionalType->getName() === $type->getName()) {
0 ignored issues
show
Bug introduced by
The method getName() does not exist on Digia\GraphQL\Type\Definition\TypeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Type\Defin...n\WrappingTypeInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

194
        if (get_class($conditionalType) === get_class($type) && $conditionalType->/** @scrutinizer ignore-call */ getName() === $type->getName()) {
Loading history...
195
            return true;
196
        }
197
198
        if ($conditionalType instanceof AbstractTypeInterface) {
199
            return $this->context->getSchema()->isPossibleType($conditionalType, $type);
200
        }
201
202
        return false;
203
    }
204
205
    /**
206
     * @TODO: consider to move this to FieldNode
207
     * @param FieldNode $node
208
     * @return string
209
     */
210
    private function getFieldNameKey(FieldNode $node): string
211
    {
212
        return $node->getAlias() ? $node->getAlias()->getValue() : $node->getNameValue();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node->getAlias()...: $node->getNameValue() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
213
    }
214
215
    /**
216
     * Implements the "Evaluating selection sets" section of the spec for "read" mode.
217
     * @param ObjectType $objectType
218
     * @param            $rootValue
219
     * @param            $path
220
     * @param            $fields
221
     * @return array
222
     * @throws InvalidTypeException
223
     * @throws \Digia\GraphQL\Error\ExecutionException
224
     * @throws \Digia\GraphQL\Error\InvariantException
225
     * @throws \Throwable
226
     */
227
    protected function executeFields(
228
        ObjectType $objectType,
229
        $rootValue,
230
        $path,
231
        $fields
232
    ): array {
233
        $finalResults = [];
234
235
        foreach ($fields as $fieldName => $fieldNodes) {
236
            $fieldPath   = $path;
237
            $fieldPath[] = $fieldName;
238
239
            $result = $this->resolveField(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->resolveField($obj...fieldNodes, $fieldPath) targeting Digia\GraphQL\Execution\...trategy::resolveField() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
240
                $objectType,
241
                $rootValue,
242
                $fieldNodes,
243
                $fieldPath
244
            );
245
246
            $finalResults[$fieldName] = $result;
247
        }
248
249
        return $finalResults;
250
    }
251
252
    /**
253
     * Implements the "Evaluating selection sets" section of the spec for "write" mode.
254
     *
255
     * @param ObjectType $objectType
256
     * @param            $rootValue
257
     * @param            $path
258
     * @param            $fields
259
     * @return array
260
     * @throws InvalidTypeException
261
     * @throws \Digia\GraphQL\Error\ExecutionException
262
     * @throws \Digia\GraphQL\Error\InvariantException
263
     * @throws \Throwable
264
     */
265
    public function executeFieldsSerially(
266
        ObjectType $objectType,
267
        $rootValue,
268
        $path,
269
        $fields
270
    ) {
271
        //@TODO execute fields serially
272
        $finalResults = [];
273
274
        foreach ($fields as $fieldName => $fieldNodes) {
275
            $fieldPath   = $path;
276
            $fieldPath[] = $fieldName;
277
278
            $result = $this->resolveField(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->resolveField($obj...fieldNodes, $fieldPath) targeting Digia\GraphQL\Execution\...trategy::resolveField() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
279
                $objectType,
280
                $rootValue,
281
                $fieldNodes,
282
                $fieldPath
283
            );
284
285
            $finalResults[$fieldName] = $result;
286
        }
287
288
        return $finalResults;
289
    }
290
291
    /**
292
     * @param Schema     $schema
293
     * @param ObjectType $parentType
294
     * @param string     $fieldName
295
     * @return \Digia\GraphQL\Type\Definition\Field|null
296
     * @throws InvalidTypeException
297
     */
298
    public function getFieldDefinition(
299
        Schema $schema,
300
        ObjectType $parentType,
301
        string $fieldName
302
    ) {
303
        $schemaMetaFieldDefinition   = SchemaMetaFieldDefinition();
304
        $typeMetaFieldDefinition     = TypeMetaFieldDefinition();
305
        $typeNameMetaFieldDefinition = TypeNameMetaFieldDefinition();
306
307
        if ($fieldName === $schemaMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
308
            return $schemaMetaFieldDefinition;
309
        }
310
311
        if ($fieldName === $typeMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
312
            return $typeMetaFieldDefinition;
313
        }
314
315
        if ($fieldName === $typeNameMetaFieldDefinition->getName()) {
316
            return $typeNameMetaFieldDefinition;
317
        }
318
319
        $fields = $parentType->getFields();
320
321
        return $fields[$fieldName] ?? null;
322
    }
323
324
325
    /**
326
     * @param ObjectType $parentType
327
     * @param            $rootValue
328
     * @param            $fieldNodes
329
     * @param            $path
330
     * @return array|null|\Throwable
331
     * @throws InvalidTypeException
332
     * @throws \Digia\GraphQL\Error\ExecutionException
333
     * @throws \Digia\GraphQL\Error\InvariantException
334
     * @throws \Throwable
335
     */
336
    protected function resolveField(
337
        ObjectType $parentType,
338
        $rootValue,
339
        $fieldNodes,
340
        $path
341
    ) {
342
        /** @var FieldNode $fieldNode */
343
        $fieldNode = $fieldNodes[0];
344
345
        $field = $this->getFieldDefinition($this->context->getSchema(), $parentType, $fieldNode->getNameValue());
346
347
        if (!$field) {
348
            return null;
349
        }
350
351
        $info = $this->buildResolveInfo($fieldNodes, $fieldNode, $field, $parentType, $path, $this->context);
352
353
        $resolveFunction = $this->determineResolveFunction($field, $parentType, $this->context);
354
355
        $result = $this->resolveFieldValueOrError(
356
            $field,
357
            $fieldNode,
358
            $resolveFunction,
359
            $rootValue,
360
            $this->context,
361
            $info
362
        );
363
364
        $result = $this->completeValueCatchingError(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $result is correct as $this->completeValueCatc... $info, $path, $result) targeting Digia\GraphQL\Execution\...eteValueCatchingError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
365
            $field->getType(),
366
            $fieldNodes,
367
            $info,
368
            $path,
369
            $result// $result is passed as $source
370
        );
371
372
        return $result;
373
    }
374
375
    /**
376
     * @param array            $fieldNodes
377
     * @param FieldNode        $fieldNode
378
     * @param Field            $field
379
     * @param ObjectType       $parentType
380
     * @param                  $path
381
     * @param ExecutionContext $context
382
     * @return ResolveInfo
383
     */
384
    private function buildResolveInfo(
385
        array $fieldNodes,
386
        FieldNode $fieldNode,
387
        Field $field,
388
        ObjectType $parentType,
389
        $path,
390
        ExecutionContext $context
391
    ) {
392
        return new ResolveInfo([
393
            'fieldName'      => $fieldNode->getNameValue(),
394
            'fieldNodes'     => $fieldNodes,
395
            'returnType'     => $field->getType(),
396
            'parentType'     => $parentType,
397
            'path'           => $path,
398
            'schema'         => $context->getSchema(),
399
            'fragments'      => $context->getFragments(),
400
            'rootValue'      => $context->getRootValue(),
401
            'operation'      => $context->getOperation(),
402
            'variableValues' => $context->getVariableValues(),
403
        ]);
404
    }
405
406
    /**
407
     * @param Field            $field
408
     * @param ObjectType       $objectType
409
     * @param ExecutionContext $context
410
     * @return callable|mixed|null
411
     */
412
    private function determineResolveFunction(
413
        Field $field,
414
        ObjectType $objectType,
415
        ExecutionContext $context
416
    ) {
417
418
        if ($field->hasResolve()) {
419
            return $field->getResolve();
420
        }
421
422
        if ($objectType->hasResolve()) {
423
            return $objectType->getResolve();
424
        }
425
426
        return $this->context->getFieldResolver() ?? self::$defaultFieldResolver;
427
    }
428
429
    /**
430
     * @param TypeInterface $fieldType
431
     * @param               $fieldNodes
432
     * @param ResolveInfo   $info
433
     * @param               $path
434
     * @param               $result
435
     * @return null
436
     * @throws \Throwable
437
     */
438
    public function completeValueCatchingError(
439
        TypeInterface $fieldType,
440
        $fieldNodes,
441
        ResolveInfo $info,
442
        $path,
443
        &$result
444
    ) {
445
        if ($fieldType instanceof NonNullType) {
446
            return $this->completeValueWithLocatedError(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->completeVa... $info, $path, $result) also could return the type array which is incompatible with the documented return type null.
Loading history...
447
                $fieldType,
448
                $fieldNodes,
449
                $info,
450
                $path,
451
                $result
452
            );
453
        }
454
455
        try {
456
            $completed = $this->completeValueWithLocatedError(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $completed is correct as $this->completeValueWith... $info, $path, $result) targeting Digia\GraphQL\Execution\...ValueWithLocatedError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
457
                $fieldType,
458
                $fieldNodes,
459
                $info,
460
                $path,
461
                $result
462
            );
463
464
            return $completed;
465
        } catch (\Exception $ex) {
466
            $this->context->addError(new ExecutionException($ex->getMessage()));
467
            return null;
468
        }
469
    }
470
471
    /**
472
     * @param TypeInterface $fieldType
473
     * @param               $fieldNodes
474
     * @param ResolveInfo   $info
475
     * @param               $path
476
     * @param               $result
477
     * @throws \Throwable
478
     */
479
    public function completeValueWithLocatedError(
480
        TypeInterface $fieldType,
481
        $fieldNodes,
482
        ResolveInfo $info,
483
        $path,
484
        $result
485
    ) {
486
        try {
487
            $completed = $this->completeValue(
488
                $fieldType,
489
                $fieldNodes,
490
                $info,
491
                $path,
492
                $result
493
            );
494
            return $completed;
495
        } catch (\Exception $ex) {
496
            //@TODO throw located error
497
            throw $ex;
498
        } catch (\Throwable $ex) {
499
            //@TODO throw located error
500
            throw $ex;
501
        }
502
    }
503
504
    /**
505
     * @param TypeInterface $returnType
506
     * @param               $fieldNodes
507
     * @param ResolveInfo   $info
508
     * @param               $path
509
     * @param               $result
510
     * @return array|mixed
511
     * @throws ExecutionException
512
     * @throws \Throwable
513
     */
514
    private function completeValue(
515
        TypeInterface $returnType,
516
        $fieldNodes,
517
        ResolveInfo $info,
518
        $path,
519
        &$result
520
    ) {
521
        if ($result instanceof \Throwable) {
522
            throw $result;
523
        }
524
525
        // If result is null-like, return null.
526
        if (null === $result) {
527
            return null;
528
        }
529
530
        if ($returnType instanceof NonNullType) {
531
            $completed = $this->completeValue(
532
                $returnType->getOfType(),
533
                $fieldNodes,
534
                $info,
535
                $path,
536
                $result
537
            );
538
539
            if ($completed === null) {
540
                throw new ExecutionException(
541
                    sprintf(
542
                        'Cannot return null for non-nullable field %s.%s.',
543
                        $info->getParentType(), $info->getFieldName()
544
                    )
545
                );
546
            }
547
548
            return $completed;
549
        }
550
551
        // If field type is List, complete each item in the list with the inner type
552
        if ($returnType instanceof ListType) {
553
            return $this->completeListValue($returnType, $fieldNodes, $info, $path, $result);
554
        }
555
556
557
        // If field type is Scalar or Enum, serialize to a valid value, returning
558
        // null if serialization is not possible.
559
        if ($returnType instanceof LeafTypeInterface) {
560
            return $this->completeLeafValue($returnType, $result);
561
        }
562
563
        //@TODO Make a function for checking abstract type?
564
        if ($returnType instanceof InterfaceType || $returnType instanceof UnionType) {
565
            return $this->completeAbstractValue($returnType, $fieldNodes, $info, $path, $result);
566
        }
567
568
        // Field type must be Object, Interface or Union and expect sub-selections.
569
        if ($returnType instanceof ObjectType) {
570
            return $this->completeObjectValue($returnType, $fieldNodes, $info, $path, $result);
571
        }
572
573
        throw new ExecutionException("Cannot complete value of unexpected type \"{$returnType}\".");
574
    }
575
576
    /**
577
     * @param AbstractTypeInterface $returnType
578
     * @param                       $fieldNodes
579
     * @param ResolveInfo           $info
580
     * @param                       $path
581
     * @param                       $result
582
     * @return array
583
     * @throws ExecutionException
584
     * @throws InvalidTypeException
585
     * @throws \Digia\GraphQL\Error\InvariantException
586
     * @throws \Throwable
587
     */
588
    private function completeAbstractValue(
589
        AbstractTypeInterface $returnType,
590
        $fieldNodes,
591
        ResolveInfo $info,
592
        $path,
593
        &$result
594
    ) {
595
        $runtimeType = $returnType->resolveType($result, $this->context, $info);
596
597
        if (null === $runtimeType) {
598
            throw new ExecutionException(
599
                sprintf(
600
                    "GraphQL Interface Type `%s` returned `null` from it`s `resolveType` function for value: %s",
601
                    $returnType->getName(), toString($result)
602
                )
603
            );
604
        }
605
606
        //@TODO Check if $runtimeType is a valid runtime type
607
        return $this->completeObjectValue(
608
            $runtimeType,
609
            $fieldNodes,
610
            $info,
611
            $path,
612
            $result
613
        );
614
    }
615
616
    /**
617
     * @param ListType    $returnType
618
     * @param             $fieldNodes
619
     * @param ResolveInfo $info
620
     * @param             $path
621
     * @param             $result
622
     * @return array
623
     * @throws \Throwable
624
     */
625
    private function completeListValue(
626
        ListType $returnType,
627
        $fieldNodes,
628
        ResolveInfo $info,
629
        $path,
630
        &$result
631
    ) {
632
        $itemType = $returnType->getOfType();
633
634
        $completedItems = [];
635
636
        foreach ($result as $key => $item) {
637
            $fieldPath        = $path;
638
            $fieldPath[]      = $key;
639
            $completedItem    = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $completedItem is correct as $this->completeValueCatc...nfo, $fieldPath, $item) targeting Digia\GraphQL\Execution\...eteValueCatchingError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
640
            $completedItems[] = $completedItem;
641
        }
642
643
        return $completedItems;
644
    }
645
646
    /**
647
     * @param LeafTypeInterface $returnType
648
     * @param                   $result
649
     * @return mixed
650
     * @throws ExecutionException
651
     */
652
    private function completeLeafValue(LeafTypeInterface $returnType, &$result)
653
    {
654
        $serializedResult = $returnType->serialize($result);
0 ignored issues
show
Bug introduced by
The method serialize() does not exist on Digia\GraphQL\Type\Definition\LeafTypeInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Digia\GraphQL\Type\Definition\LeafTypeInterface. ( Ignorable by Annotation )

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

654
        /** @scrutinizer ignore-call */ 
655
        $serializedResult = $returnType->serialize($result);
Loading history...
655
656
        if ($serializedResult === null) {
657
            throw new ExecutionException(
658
                sprintf('Expected a value of type "%s" but received: %s', toString($returnType), toString($result))
659
            );
660
        }
661
662
        return $serializedResult;
663
    }
664
665
    /**
666
     * @param ObjectType  $returnType
667
     * @param             $fieldNodes
668
     * @param ResolveInfo $info
669
     * @param             $path
670
     * @param             $result
671
     * @return array
672
     * @throws ExecutionException
673
     * @throws InvalidTypeException
674
     * @throws \Digia\GraphQL\Error\InvariantException
675
     * @throws \Throwable
676
     */
677
    private function completeObjectValue(
678
        ObjectType $returnType,
679
        $fieldNodes,
680
        ResolveInfo $info,
681
        $path,
682
        &$result
683
    ) {
684
        return $this->collectAndExecuteSubFields(
685
            $returnType,
686
            $fieldNodes,
687
            $info,
688
            $path,
689
            $result
690
        );
691
    }
692
693
    /**
694
     * @param Field            $field
695
     * @param FieldNode        $fieldNode
696
     * @param callable         $resolveFunction
697
     * @param                  $rootValue
698
     * @param ExecutionContext $context
699
     * @param ResolveInfo      $info
700
     * @return array|\Throwable
701
     */
702
    private function resolveFieldValueOrError(
703
        Field $field,
704
        FieldNode $fieldNode,
705
        ?callable $resolveFunction,
706
        $rootValue,
707
        ExecutionContext $context,
708
        ResolveInfo $info
709
    ) {
710
        try {
711
            $args = $this->valuesResolver->coerceArgumentValues($field, $fieldNode, $context->getVariableValues());
712
713
            return $resolveFunction($rootValue, $args, $context->getContextValue(), $info);
714
        } catch (\Throwable $error) {
715
            return $error;
716
        }
717
    }
718
719
    /**
720
     * Try to resolve a field without any field resolver function.
721
     *
722
     * @param array|object $rootValue
723
     * @param              $args
724
     * @param              $context
725
     * @param ResolveInfo  $info
726
     * @return mixed|null
727
     */
728
    public static function defaultFieldResolver($rootValue, $args, $context, ResolveInfo $info)
729
    {
730
        $fieldName = $info->getFieldName();
731
        $property  = null;
732
733
        if (is_array($rootValue) && isset($rootValue[$fieldName])) {
734
            $property = $rootValue[$fieldName];
735
        }
736
737
        if (is_object($rootValue)) {
738
            $getter = 'get' . ucfirst($fieldName);
739
            if (method_exists($rootValue, $fieldName)) {
740
                $property = $rootValue->{$getter}();
741
            }
742
743
            if (property_exists($rootValue, $fieldName)) {
744
                $property = $rootValue->{$fieldName};
745
            }
746
        }
747
748
749
        return $property instanceof \Closure ? $property($rootValue, $args, $context, $info) : $property;
750
    }
751
752
    /**
753
     * @param ObjectType  $returnType
754
     * @param             $fieldNodes
755
     * @param ResolveInfo $info
756
     * @param             $path
757
     * @param             $result
758
     * @return array
759
     * @throws InvalidTypeException
760
     * @throws \Digia\GraphQL\Error\ExecutionException
761
     * @throws \Digia\GraphQL\Error\InvariantException
762
     * @throws \Throwable
763
     */
764
    private function collectAndExecuteSubFields(
765
        ObjectType $returnType,
766
        $fieldNodes,
767
        ResolveInfo $info,
768
        $path,
769
        &$result
770
    ) {
771
        $subFields = [];
772
        $visitedFragmentNames = [];
773
774
        foreach ($fieldNodes as $fieldNode) {
775
            /** @var FieldNode $fieldNode */
776
            if ($fieldNode->getSelectionSet() !== null) {
777
                $subFields = $this->collectFields(
778
                    $returnType,
779
                    $fieldNode->getSelectionSet(),
780
                    $subFields,
781
                    $visitedFragmentNames
782
                );
783
            }
784
        }
785
786
        if (!empty($subFields)) {
787
            return $this->executeFields($returnType, $result, $path, $subFields);
788
        }
789
790
        return $result;
791
    }
792
}
793