Passed
Pull Request — master (#119)
by Quang
02:29
created

ExecutionStrategy::completeObjectValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 5
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
        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(
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
                $objectType,
278
                $rootValue,
279
                $fieldNodes,
280
                $fieldPath
281
            );
282
283
            $finalResults[$fieldName] = $result;
284
        }
285
286
        return $finalResults;
287
    }
288
289
    /**
290
     * @param Schema     $schema
291
     * @param ObjectType $parentType
292
     * @param string     $fieldName
293
     * @return \Digia\GraphQL\Type\Definition\Field|null
294
     * @throws InvalidTypeException
295
     */
296
    public function getFieldDefinition(
297
        Schema $schema,
298
        ObjectType $parentType,
299
        string $fieldName
300
    ) {
301
        $schemaMetaFieldDefinition   = SchemaMetaFieldDefinition();
302
        $typeMetaFieldDefinition     = TypeMetaFieldDefinition();
303
        $typeNameMetaFieldDefinition = TypeNameMetaFieldDefinition();
304
305
        if ($fieldName === $schemaMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
306
            return $schemaMetaFieldDefinition;
307
        }
308
309
        if ($fieldName === $typeMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
310
            return $typeMetaFieldDefinition;
311
        }
312
313
        if ($fieldName === $typeNameMetaFieldDefinition->getName()) {
314
            return $typeNameMetaFieldDefinition;
315
        }
316
317
        $fields = $parentType->getFields();
318
319
        return $fields[$fieldName] ?? null;
320
    }
321
322
323
    /**
324
     * @param ObjectType $parentType
325
     * @param            $rootValue
326
     * @param            $fieldNodes
327
     * @param            $path
328
     * @return array|null|\Throwable
329
     * @throws InvalidTypeException
330
     * @throws \Digia\GraphQL\Error\ExecutionException
331
     * @throws \Digia\GraphQL\Error\InvariantException
332
     * @throws \Throwable
333
     */
334
    protected function resolveField(
335
        ObjectType $parentType,
336
        $rootValue,
337
        $fieldNodes,
338
        $path
339
    ) {
340
        /** @var FieldNode $fieldNode */
341
        $fieldNode = $fieldNodes[0];
342
343
        $field = $this->getFieldDefinition($this->context->getSchema(), $parentType, $fieldNode->getNameValue());
344
345
        if (!$field) {
346
            return null;
347
        }
348
349
        $info = $this->buildResolveInfo($fieldNodes, $fieldNode, $field, $parentType, $path, $this->context);
350
351
        $resolveFunction = $this->determineResolveFunction($field, $parentType, $this->context);
352
353
        $result = $this->resolveFieldValueOrError(
354
            $field,
355
            $fieldNode,
356
            $resolveFunction,
357
            $rootValue,
358
            $this->context,
359
            $info
360
        );
361
362
        $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...
363
            $field->getType(),
364
            $fieldNodes,
365
            $info,
366
            $path,
367
            $result// $result is passed as $source
368
        );
369
370
        return $result;
371
    }
372
373
    /**
374
     * @param array            $fieldNodes
375
     * @param FieldNode        $fieldNode
376
     * @param Field            $field
377
     * @param ObjectType       $parentType
378
     * @param                  $path
379
     * @param ExecutionContext $context
380
     * @return ResolveInfo
381
     */
382
    private function buildResolveInfo(
383
        array $fieldNodes,
384
        FieldNode $fieldNode,
385
        Field $field,
386
        ObjectType $parentType,
387
        $path,
388
        ExecutionContext $context
389
    ) {
390
        return new ResolveInfo([
391
            'fieldName'      => $fieldNode->getNameValue(),
392
            'fieldNodes'     => $fieldNodes,
393
            'returnType'     => $field->getType(),
394
            'parentType'     => $parentType,
395
            'path'           => $path,
396
            'schema'         => $context->getSchema(),
397
            'fragments'      => $context->getFragments(),
398
            'rootValue'      => $context->getRootValue(),
399
            'operation'      => $context->getOperation(),
400
            'variableValues' => $context->getVariableValues(),
401
        ]);
402
    }
403
404
    /**
405
     * @param Field            $field
406
     * @param ObjectType       $objectType
407
     * @param ExecutionContext $context
408
     * @return callable|mixed|null
409
     */
410
    private function determineResolveFunction(
411
        Field $field,
412
        ObjectType $objectType,
413
        ExecutionContext $context
414
    ) {
415
416
        if ($field->hasResolve()) {
417
            return $field->getResolve();
418
        }
419
420
        if ($objectType->hasResolve()) {
421
            return $objectType->getResolve();
422
        }
423
424
        return $this->context->getFieldResolver() ?? self::$defaultFieldResolver;
425
    }
426
427
    /**
428
     * @param TypeInterface $fieldType
429
     * @param               $fieldNodes
430
     * @param ResolveInfo   $info
431
     * @param               $path
432
     * @param               $result
433
     * @return null
434
     * @throws \Throwable
435
     */
436
    public function completeValueCatchingError(
437
        TypeInterface $fieldType,
438
        $fieldNodes,
439
        ResolveInfo $info,
440
        $path,
441
        &$result
442
    ) {
443
        if ($fieldType instanceof NonNullType) {
444
            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...
445
                $fieldType,
446
                $fieldNodes,
447
                $info,
448
                $path,
449
                $result
450
            );
451
        }
452
453
        try {
454
            $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...
455
                $fieldType,
456
                $fieldNodes,
457
                $info,
458
                $path,
459
                $result
460
            );
461
462
            return $completed;
463
        } catch (\Exception $ex) {
464
            $this->context->addError(new ExecutionException($ex->getMessage()));
465
            return null;
466
        }
467
    }
468
469
    /**
470
     * @param TypeInterface $fieldType
471
     * @param               $fieldNodes
472
     * @param ResolveInfo   $info
473
     * @param               $path
474
     * @param               $result
475
     * @throws \Throwable
476
     */
477
    public function completeValueWithLocatedError(
478
        TypeInterface $fieldType,
479
        $fieldNodes,
480
        ResolveInfo $info,
481
        $path,
482
        $result
483
    ) {
484
        try {
485
            $completed = $this->completeValue(
486
                $fieldType,
487
                $fieldNodes,
488
                $info,
489
                $path,
490
                $result
491
            );
492
            return $completed;
493
        } catch (\Exception $ex) {
494
            //@TODO throw located error
495
            throw $ex;
496
        } catch (\Throwable $ex) {
497
            //@TODO throw located error
498
            throw $ex;
499
        }
500
    }
501
502
    /**
503
     * @param TypeInterface $returnType
504
     * @param               $fieldNodes
505
     * @param ResolveInfo   $info
506
     * @param               $path
507
     * @param               $result
508
     * @return array|mixed
509
     * @throws ExecutionException
510
     * @throws \Throwable
511
     */
512
    private function completeValue(
513
        TypeInterface $returnType,
514
        $fieldNodes,
515
        ResolveInfo $info,
516
        $path,
517
        &$result
518
    ) {
519
        if ($result instanceof \Throwable) {
520
            throw $result;
521
        }
522
523
        // If result is null-like, return null.
524
        if (null === $result) {
525
            return null;
526
        }
527
528
        if ($returnType instanceof NonNullType) {
529
            $completed = $this->completeValue(
530
                $returnType->getOfType(),
531
                $fieldNodes,
532
                $info,
533
                $path,
534
                $result
535
            );
536
537
            if ($completed === null) {
538
                throw new ExecutionException(
539
                    sprintf(
540
                        'Cannot return null for non-nullable field %s.%s.',
541
                        $info->getParentType(), $info->getFieldName()
542
                    )
543
                );
544
            }
545
546
            return $completed;
547
        }
548
549
        // If field type is List, complete each item in the list with the inner type
550
        if ($returnType instanceof ListType) {
551
            return $this->completeListValue($returnType, $fieldNodes, $info, $path, $result);
552
        }
553
554
555
        // If field type is Scalar or Enum, serialize to a valid value, returning
556
        // null if serialization is not possible.
557
        if ($returnType instanceof LeafTypeInterface) {
558
            return $this->completeLeafValue($returnType, $result);
559
        }
560
561
        //@TODO Make a function for checking abstract type?
562
        if ($returnType instanceof InterfaceType || $returnType instanceof UnionType) {
563
            return $this->completeAbstractValue($returnType, $fieldNodes, $info, $path, $result);
564
        }
565
566
        // Field type must be Object, Interface or Union and expect sub-selections.
567
        if ($returnType instanceof ObjectType) {
568
            return $this->completeObjectValue($returnType, $fieldNodes, $info, $path, $result);
569
        }
570
571
        throw new ExecutionException("Cannot complete value of unexpected type \"{$returnType}\".");
572
    }
573
574
    /**
575
     * @param AbstractTypeInterface $returnType
576
     * @param                       $fieldNodes
577
     * @param ResolveInfo           $info
578
     * @param                       $path
579
     * @param                       $result
580
     * @return array
581
     * @throws ExecutionException
582
     * @throws InvalidTypeException
583
     * @throws \Digia\GraphQL\Error\InvariantException
584
     * @throws \Throwable
585
     */
586
    private function completeAbstractValue(
587
        AbstractTypeInterface $returnType,
588
        $fieldNodes,
589
        ResolveInfo $info,
590
        $path,
591
        &$result
592
    ) {
593
        $runtimeType = $returnType->resolveType($result, $this->context, $info);
594
595
        if (null === $runtimeType) {
596
            //@TODO Show warning
597
            $runtimeType = $this->defaultTypeResolver($result, $this->context->getContextValue(), $info, $returnType);
598
        }
599
600
        $runtimeType = $this->ensureValidRuntimeType(
601
            $runtimeType,
602
            $returnType,
603
            $fieldNodes,
604
            $info,
605
            $result
606
        );
607
608
        return $this->completeObjectValue(
609
            $runtimeType,
610
            $fieldNodes,
611
            $info,
612
            $path,
613
            $result
614
        );
615
    }
616
617
    /**
618
     * @param                       $runtimeTypeOrName
619
     * @param AbstractTypeInterface $returnType
620
     * @param                       $fieldNodes
621
     * @param ResolveInfo           $info
622
     * @param                       $result
623
     * @return TypeInterface|null
624
     * @throws ExecutionException
625
     */
626
    private function ensureValidRuntimeType(
627
        $runtimeTypeOrName,
628
        AbstractTypeInterface $returnType,
629
        $fieldNodes,
630
        ResolveInfo $info,
631
        &$result
632
    )
633
    {
634
        $runtimeType = is_string($runtimeTypeOrName)
635
            ? $this->context->getSchema()->getType($runtimeTypeOrName)
636
            : $runtimeTypeOrName;
637
638
        if (!$runtimeType instanceof ObjectType) {
639
            throw new ExecutionException(
640
                "Abstract type {$returnType->getName()} must resolve to an Object type at runtime " .
641
                "for field {$info->getParentType()}.{$info->getFieldName()} with " .
642
                'value "' . toString($result) . '", received "'. toString($runtimeType) . '".'
643
            );
644
        }
645
646
        if (!$this->context->getSchema()->isPossibleType($returnType, $runtimeType)) {
647
            throw new ExecutionException(
648
                "Runtime Object type \"$runtimeType\" is not a possible type for \"{$returnType->getName()}\"."
649
            );
650
        }
651
652
        if ($runtimeType !== $this->context->getSchema()->getType($runtimeType->getName())) {
653
            throw new ExecutionException(
654
                "Schema must contain unique named types but contains multiple types named \"{$runtimeType->getName()}\". ".
655
                "Make sure that `resolveType` function of abstract type \"{$returnType->getName()}\" returns the same ".
656
                "type instance as referenced anywhere else within the schema."
657
            );
658
        }
659
660
        return $runtimeType;
661
    }
662
663
    /**
664
     * @param                       $value
665
     * @param                       $context
666
     * @param ResolveInfo           $info
667
     * @param AbstractTypeInterface $abstractType
668
     * @return TypeInterface|null
669
     */
670
    private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractTypeInterface $abstractType)
671
    {
672
        $possibleTypes = $info->getSchema()->getPossibleTypes($abstractType);
673
674
        foreach ($possibleTypes as $index => $type) {
675
            $isTypeOfResult = $type->isTypeOf($value, $context, $info);
676
677
            if (null !== $isTypeOfResult) {
678
                if ($isTypeOfResult) {
679
                    return $type;
680
                }
681
            }
682
        }
683
684
        return null;
685
    }
686
687
    /**
688
     * @param ListType    $returnType
689
     * @param             $fieldNodes
690
     * @param ResolveInfo $info
691
     * @param             $path
692
     * @param             $result
693
     * @return array
694
     * @throws \Throwable
695
     */
696
    private function completeListValue(
697
        ListType $returnType,
698
        $fieldNodes,
699
        ResolveInfo $info,
700
        $path,
701
        &$result
702
    ) {
703
        $itemType = $returnType->getOfType();
704
705
        $completedItems = [];
706
707
        foreach ($result as $key => $item) {
708
            $fieldPath        = $path;
709
            $fieldPath[]      = $key;
710
            $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...
711
            $completedItems[] = $completedItem;
712
        }
713
714
        return $completedItems;
715
    }
716
717
    /**
718
     * @param LeafTypeInterface $returnType
719
     * @param                   $result
720
     * @return mixed
721
     * @throws ExecutionException
722
     */
723
    private function completeLeafValue(LeafTypeInterface $returnType, &$result)
724
    {
725
        $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

725
        /** @scrutinizer ignore-call */ 
726
        $serializedResult = $returnType->serialize($result);
Loading history...
726
727
        if ($serializedResult === null) {
728
            throw new ExecutionException(
729
                sprintf('Expected a value of type "%s" but received: %s', toString($returnType), toString($result))
730
            );
731
        }
732
733
        return $serializedResult;
734
    }
735
736
    /**
737
     * @param ObjectType  $returnType
738
     * @param             $fieldNodes
739
     * @param ResolveInfo $info
740
     * @param             $path
741
     * @param             $result
742
     * @return array
743
     * @throws ExecutionException
744
     * @throws InvalidTypeException
745
     * @throws \Digia\GraphQL\Error\InvariantException
746
     * @throws \Throwable
747
     */
748
    private function completeObjectValue(
749
        ObjectType $returnType,
750
        $fieldNodes,
751
        ResolveInfo $info,
752
        $path,
753
        &$result
754
    ) {
755
        return $this->collectAndExecuteSubFields(
756
            $returnType,
757
            $fieldNodes,
758
            $info,
759
            $path,
760
            $result
761
        );
762
    }
763
764
    /**
765
     * @param Field            $field
766
     * @param FieldNode        $fieldNode
767
     * @param callable         $resolveFunction
768
     * @param                  $rootValue
769
     * @param ExecutionContext $context
770
     * @param ResolveInfo      $info
771
     * @return array|\Throwable
772
     */
773
    private function resolveFieldValueOrError(
774
        Field $field,
775
        FieldNode $fieldNode,
776
        ?callable $resolveFunction,
777
        $rootValue,
778
        ExecutionContext $context,
779
        ResolveInfo $info
780
    ) {
781
        try {
782
            $args = $this->valuesResolver->coerceArgumentValues($field, $fieldNode, $context->getVariableValues());
783
784
            return $resolveFunction($rootValue, $args, $context->getContextValue(), $info);
785
        } catch (\Throwable $error) {
786
            return $error;
787
        }
788
    }
789
790
    /**
791
     * Try to resolve a field without any field resolver function.
792
     *
793
     * @param array|object $rootValue
794
     * @param              $args
795
     * @param              $context
796
     * @param ResolveInfo  $info
797
     * @return mixed|null
798
     */
799
    public static function defaultFieldResolver($rootValue, $args, $context, ResolveInfo $info)
800
    {
801
        $fieldName = $info->getFieldName();
802
        $property  = null;
803
804
        if (is_array($rootValue) && isset($rootValue[$fieldName])) {
805
            $property = $rootValue[$fieldName];
806
        }
807
808
        if (is_object($rootValue)) {
809
            $getter = 'get' . ucfirst($fieldName);
810
            if (method_exists($rootValue, $fieldName)) {
811
                $property = $rootValue->{$getter}();
812
            }
813
814
            if (property_exists($rootValue, $fieldName)) {
815
                $property = $rootValue->{$fieldName};
816
            }
817
        }
818
819
820
        return $property instanceof \Closure ? $property($rootValue, $args, $context, $info) : $property;
821
    }
822
823
    /**
824
     * @param ObjectType  $returnType
825
     * @param             $fieldNodes
826
     * @param ResolveInfo $info
827
     * @param             $path
828
     * @param             $result
829
     * @return array
830
     * @throws InvalidTypeException
831
     * @throws \Digia\GraphQL\Error\ExecutionException
832
     * @throws \Digia\GraphQL\Error\InvariantException
833
     * @throws \Throwable
834
     */
835
    private function collectAndExecuteSubFields(
836
        ObjectType $returnType,
837
        $fieldNodes,
838
        ResolveInfo $info,
839
        $path,
840
        &$result
841
    ) {
842
        $subFields = [];
843
        $visitedFragmentNames = [];
844
845
        foreach ($fieldNodes as $fieldNode) {
846
            /** @var FieldNode $fieldNode */
847
            if ($fieldNode->getSelectionSet() !== null) {
848
                $subFields = $this->collectFields(
849
                    $returnType,
850
                    $fieldNode->getSelectionSet(),
851
                    $subFields,
852
                    $visitedFragmentNames
853
                );
854
            }
855
        }
856
857
        if (!empty($subFields)) {
858
            return $this->executeFields($returnType, $result, $path, $subFields);
859
        }
860
861
        return $result;
862
    }
863
}
864