Completed
Pull Request — master (#111)
by Quang
02:25
created

ExecutionStrategy::defaultFieldResolver()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 7
nc 8
nop 4
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] = new \ArrayObject();
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
        if ($conditionalType === $type) {
193
            return true;
194
        }
195
196
        if ($conditionalType instanceof AbstractTypeInterface) {
197
            return $this->context->getSchema()->isPossibleType($conditionalType, $type);
198
        }
199
200
        return false;
201
    }
202
203
    /**
204
     * @TODO: consider to move this to FieldNode
205
     * @param FieldNode $node
206
     * @return string
207
     */
208
    private function getFieldNameKey(FieldNode $node): string
209
    {
210
        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...
211
    }
212
213
    /**
214
     * Implements the "Evaluating selection sets" section of the spec for "read" mode.
215
     * @param ObjectType $objectType
216
     * @param            $rootValue
217
     * @param            $path
218
     * @param            $fields
219
     * @return array
220
     * @throws InvalidTypeException
221
     * @throws \Digia\GraphQL\Error\ExecutionException
222
     * @throws \Digia\GraphQL\Error\InvariantException
223
     * @throws \Throwable
224
     */
225
    protected function executeFields(
226
        ObjectType $objectType,
227
        $rootValue,
228
        $path,
229
        $fields
230
    ): array {
231
        $finalResults = [];
232
233
        foreach ($fields as $fieldName => $fieldNodes) {
234
            $fieldPath   = $path;
235
            $fieldPath[] = $fieldName;
236
237
            $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...
238
                $objectType,
239
                $rootValue,
240
                $fieldNodes,
241
                $fieldPath
242
            );
243
244
            $finalResults[$fieldName] = $result;
245
        }
246
247
        return $finalResults;
248
    }
249
250
    /**
251
     * Implements the "Evaluating selection sets" section of the spec for "write" mode.
252
     *
253
     * @param ObjectType $objectType
254
     * @param            $rootValue
255
     * @param            $path
256
     * @param            $fields
257
     * @return array
258
     * @throws InvalidTypeException
259
     * @throws \Digia\GraphQL\Error\ExecutionException
260
     * @throws \Digia\GraphQL\Error\InvariantException
261
     * @throws \Throwable
262
     */
263
    public function executeFieldsSerially(
264
        ObjectType $objectType,
265
        $rootValue,
266
        $path,
267
        $fields
268
    ) {
269
        //@TODO execute fields serially
270
        $finalResults = [];
271
272
        foreach ($fields as $fieldName => $fieldNodes) {
273
            $fieldPath   = $path;
274
            $fieldPath[] = $fieldName;
275
276
            $result = $this->resolveField($objectType,
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...
277
                $rootValue,
278
                $fieldNodes,
279
                $fieldPath
280
            );
281
282
            $finalResults[$fieldName] = $result;
283
        }
284
285
        return $finalResults;
286
    }
287
288
    /**
289
     * @param Schema     $schema
290
     * @param ObjectType $parentType
291
     * @param string     $fieldName
292
     * @return \Digia\GraphQL\Type\Definition\Field|null
293
     * @throws InvalidTypeException
294
     */
295
    public function getFieldDefinition(
296
        Schema $schema,
297
        ObjectType $parentType,
298
        string $fieldName
299
    ) {
300
        $schemaMetaFieldDefinition   = SchemaMetaFieldDefinition();
301
        $typeMetaFieldDefinition     = TypeMetaFieldDefinition();
302
        $typeNameMetaFieldDefinition = TypeNameMetaFieldDefinition();
303
304
        if ($fieldName === $schemaMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
305
            return $schemaMetaFieldDefinition;
306
        }
307
308
        if ($fieldName === $typeMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
309
            return $typeMetaFieldDefinition;
310
        }
311
312
        if ($fieldName === $typeNameMetaFieldDefinition->getName()) {
313
            return $typeNameMetaFieldDefinition;
314
        }
315
316
        $fields = $parentType->getFields();
317
318
        return $fields[$fieldName] ?? null;
319
    }
320
321
322
    /**
323
     * @param ObjectType $parentType
324
     * @param            $rootValue
325
     * @param            $fieldNodes
326
     * @param            $path
327
     * @return array|null|\Throwable
328
     * @throws InvalidTypeException
329
     * @throws \Digia\GraphQL\Error\ExecutionException
330
     * @throws \Digia\GraphQL\Error\InvariantException
331
     * @throws \Throwable
332
     */
333
    protected function resolveField(
334
        ObjectType $parentType,
335
        $rootValue,
336
        $fieldNodes,
337
        $path
338
    ) {
339
        /** @var FieldNode $fieldNode */
340
        $fieldNode = $fieldNodes[0];
341
342
        $field = $this->getFieldDefinition($this->context->getSchema(), $parentType, $fieldNode->getNameValue());
343
344
        if (!$field) {
345
            return null;
346
        }
347
348
        $info = $this->buildResolveInfo($fieldNodes, $fieldNode, $field, $parentType, $path, $this->context);
349
350
        $resolveFunction = $this->determineResolveFunction($field, $parentType, $this->context);
351
352
        $result = $this->resolveFieldValueOrError(
353
            $field,
354
            $fieldNode,
355
            $resolveFunction,
356
            $rootValue,
357
            $this->context,
358
            $info
359
        );
360
361
        $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...
362
            $field->getType(),
363
            $fieldNodes,
364
            $info,
365
            $path,
366
            $result// $result is passed as $source
367
        );
368
369
        return $result;
370
    }
371
372
    /**
373
     * @param array            $fieldNodes
374
     * @param FieldNode        $fieldNode
375
     * @param Field            $field
376
     * @param ObjectType       $parentType
377
     * @param                  $path
378
     * @param ExecutionContext $context
379
     * @return ResolveInfo
380
     */
381
    private function buildResolveInfo(
382
        \ArrayAccess $fieldNodes,
383
        FieldNode $fieldNode,
384
        Field $field,
385
        ObjectType $parentType,
386
        $path,
387
        ExecutionContext $context
388
    ) {
389
        return new ResolveInfo([
390
            'fieldName'      => $fieldNode->getNameValue(),
391
            'fieldNodes'     => $fieldNodes,
392
            'returnType'     => $field->getType(),
393
            'parentType'     => $parentType,
394
            'path'           => $path,
395
            'schema'         => $context->getSchema(),
396
            'fragments'      => $context->getFragments(),
397
            'rootValue'      => $context->getRootValue(),
398
            'operation'      => $context->getOperation(),
399
            'variableValues' => $context->getVariableValues(),
400
        ]);
401
    }
402
403
    /**
404
     * @param Field            $field
405
     * @param ObjectType       $objectType
406
     * @param ExecutionContext $context
407
     * @return callable|mixed|null
408
     */
409
    private function determineResolveFunction(
410
        Field $field,
411
        ObjectType $objectType,
412
        ExecutionContext $context
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

412
        /** @scrutinizer ignore-unused */ ExecutionContext $context

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
413
    ) {
414
415
        if ($field->hasResolve()) {
416
            return $field->getResolve();
417
        }
418
419
        if ($objectType->hasResolve()) {
420
            return $objectType->getResolve();
421
        }
422
423
        return $this->context->getFieldResolver() ?? self::$defaultFieldResolver;
424
    }
425
426
    /**
427
     * @param TypeInterface $fieldType
428
     * @param               $fieldNodes
429
     * @param ResolveInfo   $info
430
     * @param               $path
431
     * @param               $result
432
     * @return null
433
     * @throws \Throwable
434
     */
435
    public function completeValueCatchingError(
436
        TypeInterface $fieldType,
437
        $fieldNodes,
438
        ResolveInfo $info,
439
        $path,
440
        &$result
441
    ) {
442
        if ($fieldType instanceof NonNullType) {
443
            return $this->completeValueWithLocatedError(
444
                $fieldType,
445
                $fieldNodes,
446
                $info,
447
                $path,
448
                $result
449
            );
450
        }
451
452
        try {
453
            $completed = $this->completeValueWithLocatedError(
454
                $fieldType,
455
                $fieldNodes,
456
                $info,
457
                $path,
458
                $result
459
            );
460
461
            return $completed;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $completed also could return the type array which is incompatible with the documented return type null.
Loading history...
462
        } catch (\Exception $ex) {
463
            $this->context->addError(new ExecutionException($ex->getMessage()));
464
            return null;
465
        }
466
    }
467
468
    /**
469
     * @param TypeInterface $fieldType
470
     * @param               $fieldNodes
471
     * @param ResolveInfo   $info
472
     * @param               $path
473
     * @param               $result
474
     * @throws \Throwable
475
     */
476
    public function completeValueWithLocatedError(
477
        TypeInterface $fieldType,
478
        $fieldNodes,
479
        ResolveInfo $info,
480
        $path,
481
        $result
482
    ) {
483
        try {
484
            $completed = $this->completeValue(
485
                $fieldType,
486
                $fieldNodes,
487
                $info,
488
                $path,
489
                $result
490
            );
491
            return $completed;
492
        } catch (\Exception $ex) {
493
            //@TODO throw located error
494
            throw $ex;
495
        } catch (\Throwable $ex) {
496
            //@TODO throw located error
497
            throw $ex;
498
        }
499
    }
500
501
    /**
502
     * @param TypeInterface $returnType
503
     * @param               $fieldNodes
504
     * @param ResolveInfo   $info
505
     * @param               $path
506
     * @param               $result
507
     * @return array|mixed
508
     * @throws ExecutionException
509
     * @throws \Throwable
510
     */
511
    private function completeValue(
512
        TypeInterface $returnType,
513
        $fieldNodes,
514
        ResolveInfo $info,
515
        $path,
516
        &$result
517
    ) {
518
        if ($result instanceof \Throwable) {
519
            throw $result;
520
        }
521
522
        // If result is null-like, return null.
523
        if (null === $result) {
524
            return null;
525
        }
526
527
        if ($returnType instanceof NonNullType) {
528
            $completed = $this->completeValue(
529
                $returnType->getOfType(),
530
                $fieldNodes,
531
                $info,
532
                $path,
533
                $result
534
            );
535
536
            if ($completed === null) {
537
                throw new ExecutionException(
538
                    sprintf(
539
                        'Cannot return null for non-nullable field %s.%s.',
540
                        $info->getParentType(), $info->getFieldName()
541
                    )
542
                );
543
            }
544
545
            return $completed;
546
        }
547
548
        // If field type is List, complete each item in the list with the inner type
549
        if ($returnType instanceof ListType) {
550
            return $this->completeListValue($returnType, $fieldNodes, $info, $path, $result);
551
        }
552
553
554
        // If field type is Scalar or Enum, serialize to a valid value, returning
555
        // null if serialization is not possible.
556
        if ($returnType instanceof LeafTypeInterface) {
557
            return $this->completeLeafValue($returnType, $result);
558
        }
559
560
        //@TODO Make a function for checking abstract type?
561
        if ($returnType instanceof InterfaceType || $returnType instanceof UnionType) {
562
            return $this->completeAbstractValue($returnType, $fieldNodes, $info, $path, $result);
563
        }
564
565
        // Field type must be Object, Interface or Union and expect sub-selections.
566
        if ($returnType instanceof ObjectType) {
567
            return $this->completeObjectValue($returnType, $fieldNodes, $info, $path, $result);
568
        }
569
570
        throw new ExecutionException("Cannot complete value of unexpected type \"{$returnType}\".");
571
    }
572
573
    /**
574
     * @param AbstractTypeInterface $returnType
575
     * @param                       $fieldNodes
576
     * @param ResolveInfo           $info
577
     * @param                       $path
578
     * @param                       $result
579
     * @return array
580
     * @throws ExecutionException
581
     * @throws InvalidTypeException
582
     * @throws \Digia\GraphQL\Error\InvariantException
583
     * @throws \Throwable
584
     */
585
    private function completeAbstractValue(
586
        AbstractTypeInterface $returnType,
587
        $fieldNodes,
588
        ResolveInfo $info,
589
        $path,
590
        &$result
591
    ) {
592
        $runtimeType = $returnType->resolveType($result, $this->context, $info);
593
594
        if (null === $runtimeType) {
595
            throw new ExecutionException(
596
                sprintf(
597
                    "GraphQL Interface Type `%s` returned `null` from it`s `resolveType` function for value: %s",
598
                    $returnType->getName(), toString($result)
599
                )
600
            );
601
        }
602
603
        //@TODO Check if $runtimeType is a valid runtime type
604
        return $this->completeObjectValue(
605
            $runtimeType,
606
            $fieldNodes,
607
            $info,
608
            $path,
609
            $result
610
        );
611
    }
612
613
    /**
614
     * @param ListType    $returnType
615
     * @param             $fieldNodes
616
     * @param ResolveInfo $info
617
     * @param             $path
618
     * @param             $result
619
     * @return array
620
     * @throws \Throwable
621
     */
622
    private function completeListValue(
623
        ListType $returnType,
624
        $fieldNodes,
625
        ResolveInfo $info,
626
        $path,
627
        &$result
628
    ) {
629
        $itemType = $returnType->getOfType();
630
631
        $completedItems = [];
632
633
        foreach ($result as $key => $item) {
634
            $fieldPath        = $path;
635
            $fieldPath[]      = $key;
636
            $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...
637
            $completedItems[] = $completedItem;
638
        }
639
640
        return $completedItems;
641
    }
642
643
    /**
644
     * @param LeafTypeInterface $returnType
645
     * @param                   $result
646
     * @return mixed
647
     * @throws ExecutionException
648
     */
649
    private function completeLeafValue(LeafTypeInterface $returnType, &$result)
650
    {
651
        $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

651
        /** @scrutinizer ignore-call */ 
652
        $serializedResult = $returnType->serialize($result);
Loading history...
652
653
        if ($serializedResult === null) {
654
            throw new ExecutionException(
655
                sprintf('Expected a value of type "%s" but received: %s', toString($returnType), toString($result))
656
            );
657
        }
658
659
        return $serializedResult;
660
    }
661
662
    /**
663
     * @param ObjectType  $returnType
664
     * @param             $fieldNodes
665
     * @param ResolveInfo $info
666
     * @param             $path
667
     * @param             $result
668
     * @return array
669
     * @throws ExecutionException
670
     * @throws InvalidTypeException
671
     * @throws \Digia\GraphQL\Error\InvariantException
672
     * @throws \Throwable
673
     */
674
    private function completeObjectValue(
675
        ObjectType $returnType,
676
        $fieldNodes,
677
        ResolveInfo $info,
678
        $path,
679
        &$result
680
    ) {
681
        return $this->collectAndExecuteSubFields(
682
            $returnType,
683
            $fieldNodes,
684
            $info,
685
            $path,
686
            $result
687
        );
688
    }
689
690
    /**
691
     * @param Field            $field
692
     * @param FieldNode        $fieldNode
693
     * @param callable         $resolveFunction
694
     * @param                  $rootValue
695
     * @param ExecutionContext $context
696
     * @param ResolveInfo      $info
697
     * @return array|\Throwable
698
     */
699
    private function resolveFieldValueOrError(
700
        Field $field,
701
        FieldNode $fieldNode,
702
        ?callable $resolveFunction,
703
        $rootValue,
704
        ExecutionContext $context,
705
        ResolveInfo $info
706
    ) {
707
        try {
708
            $args = $this->valuesResolver->coerceArgumentValues($field, $fieldNode, $context->getVariableValues());
709
710
            return $resolveFunction($rootValue, $args, $context->getContextValue(), $info);
711
        } catch (\Throwable $error) {
712
            return $error;
713
        }
714
    }
715
716
    /**
717
     * Try to resolve a field without any field resolver function.
718
     *
719
     * @param array|object $rootValue
720
     * @param              $args
721
     * @param              $context
722
     * @param ResolveInfo  $info
723
     * @return mixed|null
724
     */
725
    public static function defaultFieldResolver($rootValue, $args, $context, ResolveInfo $info)
726
    {
727
        $fieldName = $info->getFieldName();
728
        $property  = null;
729
730
        if (is_array($rootValue) && isset($rootValue[$fieldName])) {
731
            $property = $rootValue[$fieldName];
732
        }
733
734
        if (is_object($rootValue) && method_exists($rootValue, $fieldName)) {
735
            $property = $rootValue->{$fieldName};
736
        }
737
738
739
        return $property instanceof \Closure ? $property($rootValue, $args, $context, $info) : $property;
740
    }
741
742
    /**
743
     * @param ObjectType  $returnType
744
     * @param             $fieldNodes
745
     * @param ResolveInfo $info
746
     * @param             $path
747
     * @param             $result
748
     * @return array
749
     * @throws InvalidTypeException
750
     * @throws \Digia\GraphQL\Error\ExecutionException
751
     * @throws \Digia\GraphQL\Error\InvariantException
752
     * @throws \Throwable
753
     */
754
    private function collectAndExecuteSubFields(
755
        ObjectType $returnType,
756
        $fieldNodes,
757
        ResolveInfo $info,
0 ignored issues
show
Unused Code introduced by
The parameter $info is not used and could be removed. ( Ignorable by Annotation )

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

757
        /** @scrutinizer ignore-unused */ ResolveInfo $info,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
758
        $path,
759
        &$result
760
    ) {
761
        $subFields = new \ArrayObject();
762
763
        foreach ($fieldNodes as $fieldNode) {
764
            /** @var FieldNode $fieldNode */
765
            if ($fieldNode->getSelectionSet() !== null) {
766
                $subFields = $this->collectFields(
767
                    $returnType,
768
                    $fieldNode->getSelectionSet(),
769
                    $subFields,
770
                    new \ArrayObject()
771
                );
772
            }
773
        }
774
775
        if ($subFields->count()) {
776
            return $this->executeFields($returnType, $result, $path, $subFields);
777
        }
778
779
        return $result;
780
    }
781
}
782