Completed
Pull Request — master (#119)
by Quang
02:43
created

ExecutionStrategy::doesFragmentConditionMatch()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
cc 4
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
        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
        //@TODO Check if $runtimeType is a valid runtime type
609
        return $this->completeObjectValue(
610
            $runtimeType,
611
            $fieldNodes,
612
            $info,
613
            $path,
614
            $result
615
        );
616
    }
617
618
    /**
619
     * @param                       $runtimeTypeOrName
620
     * @param AbstractTypeInterface $returnType
621
     * @param                       $fieldNodes
622
     * @param ResolveInfo           $info
623
     * @param                       $result
624
     * @return TypeInterface|null
625
     * @throws ExecutionException
626
     */
627
    private function ensureValidRuntimeType(
628
        $runtimeTypeOrName,
629
        AbstractTypeInterface $returnType,
630
        $fieldNodes,
631
        ResolveInfo $info,
632
        &$result
633
    )
634
    {
635
        $runtimeType = is_string($runtimeTypeOrName)
636
            ? $this->context->getSchema()->getType($runtimeTypeOrName)
637
            : $runtimeTypeOrName;
638
639
        if (!$runtimeType instanceof ObjectType) {
640
            throw new ExecutionException(
641
                "Abstract type {$returnType->getName()} must resolve to an Object type at runtime " .
642
                "for field {$info->getParentType()}.{$info->getFieldName()} with " .
643
                'value "' . toString($result) . '", received "'. toString($runtimeType) . '".'
644
            );
645
        }
646
647
        if (!$this->context->getSchema()->isPossibleType($returnType, $runtimeType)) {
648
            throw new ExecutionException(
649
                "Runtime Object type \"$runtimeType\" is not a possible type for \"{$returnType->getName()}\"."
650
            );
651
        }
652
653
        if ($runtimeType !== $this->context->getSchema()->getType($runtimeType->getName())) {
654
            throw new ExecutionException(
655
                "Schema must contain unique named types but contains multiple types named \"{$runtimeType->getName()}\". ".
656
                "Make sure that `resolveType` function of abstract type \"{$returnType->getName()}\" returns the same ".
657
                "type instance as referenced anywhere else within the schema"
658
            );
659
        }
660
661
        return $runtimeType;
662
    }
663
664
    /**
665
     * @param                       $value
666
     * @param                       $context
667
     * @param ResolveInfo           $info
668
     * @param AbstractTypeInterface $abstractType
669
     * @return TypeInterface|null
670
     */
671
    private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractTypeInterface $abstractType)
672
    {
673
        $possibleTypes = $info->getSchema()->getPossibleTypes($abstractType);
674
675
        foreach ($possibleTypes as $index => $type) {
676
            $isTypeOfResult = $type->isTypeOf($value, $context, $info);
677
678
            if (null !== $isTypeOfResult) {
679
                if ($isTypeOfResult) {
680
                    return $type;
681
                }
682
            }
683
        }
684
685
        return null;
686
    }
687
688
    /**
689
     * @param ListType    $returnType
690
     * @param             $fieldNodes
691
     * @param ResolveInfo $info
692
     * @param             $path
693
     * @param             $result
694
     * @return array
695
     * @throws \Throwable
696
     */
697
    private function completeListValue(
698
        ListType $returnType,
699
        $fieldNodes,
700
        ResolveInfo $info,
701
        $path,
702
        &$result
703
    ) {
704
        $itemType = $returnType->getOfType();
705
706
        $completedItems = [];
707
708
        foreach ($result as $key => $item) {
709
            $fieldPath        = $path;
710
            $fieldPath[]      = $key;
711
            $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...
712
            $completedItems[] = $completedItem;
713
        }
714
715
        return $completedItems;
716
    }
717
718
    /**
719
     * @param LeafTypeInterface $returnType
720
     * @param                   $result
721
     * @return mixed
722
     * @throws ExecutionException
723
     */
724
    private function completeLeafValue(LeafTypeInterface $returnType, &$result)
725
    {
726
        $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

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