Passed
Pull Request — master (#130)
by Quang
02:08
created

ExecutionStrategy::executeFieldsSerially()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 3
eloc 10
nc 3
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\Error\UndefinedFieldException;
8
use Digia\GraphQL\Execution\Resolver\ResolveInfo;
9
use Digia\GraphQL\Language\Node\FieldNode;
10
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
11
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
12
use Digia\GraphQL\Language\Node\InlineFragmentNode;
13
use Digia\GraphQL\Language\Node\NodeInterface;
14
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
15
use Digia\GraphQL\Language\Node\SelectionSetNode;
16
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
17
use Digia\GraphQL\Type\Definition\Field;
18
use Digia\GraphQL\Type\Definition\InterfaceType;
19
use Digia\GraphQL\Type\Definition\LeafTypeInterface;
20
use Digia\GraphQL\Type\Definition\ListType;
21
use Digia\GraphQL\Type\Definition\NonNullType;
22
use Digia\GraphQL\Type\Definition\ObjectType;
23
use Digia\GraphQL\Type\Definition\TypeInterface;
24
use Digia\GraphQL\Type\Definition\UnionType;
25
use Digia\GraphQL\Type\Schema;
26
use function Digia\GraphQL\Type\SchemaMetaFieldDefinition;
27
use function Digia\GraphQL\Type\TypeMetaFieldDefinition;
28
use function Digia\GraphQL\Type\TypeNameMetaFieldDefinition;
29
use function Digia\GraphQL\Util\toString;
30
use function Digia\GraphQL\Util\typeFromAST;
31
32
/**
33
 * Class AbstractStrategy
34
 * @package Digia\GraphQL\Execution\Strategies
35
 */
36
abstract class ExecutionStrategy
37
{
38
    /**
39
     * @var ExecutionContext
40
     */
41
    protected $context;
42
43
    /**
44
     * @var OperationDefinitionNode
45
     */
46
    protected $operation;
47
48
    /**
49
     * @var mixed
50
     */
51
    protected $rootValue;
52
53
54
    /**
55
     * @var ValuesResolver
56
     */
57
    protected $valuesResolver;
58
59
    /**
60
     * @var array
61
     */
62
    protected $finalResult;
63
64
    /**
65
     * @var array
66
     */
67
    protected static $defaultFieldResolver = [__CLASS__, 'defaultFieldResolver'];
68
69
    /**
70
     * AbstractStrategy constructor.
71
     * @param ExecutionContext        $context
72
     *
73
     * @param OperationDefinitionNode $operation
74
     */
75
    public function __construct(
76
        ExecutionContext $context,
77
        OperationDefinitionNode $operation,
78
        $rootValue
79
    ) {
80
        $this->context        = $context;
81
        $this->operation      = $operation;
82
        $this->rootValue      = $rootValue;
83
        $this->valuesResolver = new ValuesResolver();
84
    }
85
86
    /**
87
     * @return array|null
88
     */
89
    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...
90
91
    /**
92
     * @param ObjectType       $runtimeType
93
     * @param SelectionSetNode $selectionSet
94
     * @param                  $fields
95
     * @param                  $visitedFragmentNames
96
     * @return mixed
97
     * @throws InvalidTypeException
98
     * @throws \Digia\GraphQL\Error\ExecutionException
99
     * @throws \Digia\GraphQL\Error\InvariantException
100
     */
101
    protected function collectFields(
102
        ObjectType $runtimeType,
103
        SelectionSetNode $selectionSet,
104
        &$fields,
105
        &$visitedFragmentNames
106
    ) {
107
        foreach ($selectionSet->getSelections() as $selection) {
108
            // Check if this Node should be included first
109
            if (!$this->shouldIncludeNode($selection)) {
110
                continue;
111
            }
112
            // Collect fields
113
            if ($selection instanceof FieldNode) {
114
                $fieldName = $this->getFieldNameKey($selection);
115
116
                if (!isset($fields[$fieldName])) {
117
                    $fields[$fieldName] = [];
118
                }
119
120
                $fields[$fieldName][] = $selection;
121
            } elseif ($selection instanceof InlineFragmentNode) {
122
                if (!$this->doesFragmentConditionMatch($selection, $runtimeType)) {
123
                    continue;
124
                }
125
126
                $this->collectFields($runtimeType, $selection->getSelectionSet(), $fields, $visitedFragmentNames);
127
            } elseif ($selection instanceof FragmentSpreadNode) {
128
                $fragmentName = $selection->getNameValue();
129
130
                if (!empty($visitedFragmentNames[$fragmentName])) {
131
                    continue;
132
                }
133
134
                $visitedFragmentNames[$fragmentName] = true;
135
                /** @var FragmentDefinitionNode $fragment */
136
                $fragment = $this->context->getFragments()[$fragmentName];
137
                $this->collectFields($runtimeType, $fragment->getSelectionSet(), $fields, $visitedFragmentNames);
138
            }
139
        }
140
141
        return $fields;
142
    }
143
144
145
    /**
146
     * @param $node
147
     * @return bool
148
     * @throws InvalidTypeException
149
     * @throws \Digia\GraphQL\Error\ExecutionException
150
     * @throws \Digia\GraphQL\Error\InvariantException
151
     */
152
    private function shouldIncludeNode(NodeInterface $node): bool
153
    {
154
155
        $contextVariables = $this->context->getVariableValues();
156
157
        $skip = $this->valuesResolver->getDirectiveValues(GraphQLSkipDirective(), $node, $contextVariables);
158
159
        if ($skip && $skip['if'] === true) {
160
            return false;
161
        }
162
163
        $include = $this->valuesResolver->getDirectiveValues(GraphQLIncludeDirective(), $node, $contextVariables);
164
165
        if ($include && $include['if'] === false) {
166
            return false;
167
        }
168
169
        return true;
170
    }
171
172
    /**
173
     * @param FragmentDefinitionNode|InlineFragmentNode $fragment
174
     * @param ObjectType                                $type
175
     * @return bool
176
     * @throws InvalidTypeException
177
     */
178
    private function doesFragmentConditionMatch(
179
        NodeInterface $fragment,
180
        ObjectType $type
181
    ): bool {
182
        $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

182
        /** @scrutinizer ignore-call */ 
183
        $typeConditionNode = $fragment->getTypeCondition();
Loading history...
183
184
        if (!$typeConditionNode) {
185
            return true;
186
        }
187
188
        $conditionalType = typeFromAST($this->context->getSchema(), $typeConditionNode);
189
190
        if ($conditionalType === $type) {
191
            return true;
192
        }
193
194
        if ($conditionalType instanceof AbstractTypeInterface) {
195
            return $this->context->getSchema()->isPossibleType($conditionalType, $type);
196
        }
197
198
        return false;
199
    }
200
201
    /**
202
     * @TODO: consider to move this to FieldNode
203
     * @param FieldNode $node
204
     * @return string
205
     */
206
    private function getFieldNameKey(FieldNode $node): string
207
    {
208
        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...
209
    }
210
211
    /**
212
     * Implements the "Evaluating selection sets" section of the spec for "read" mode.
213
     * @param ObjectType $objectType
214
     * @param            $rootValue
215
     * @param            $path
216
     * @param            $fields
217
     * @return array
218
     * @throws InvalidTypeException
219
     * @throws \Digia\GraphQL\Error\ExecutionException
220
     * @throws \Digia\GraphQL\Error\InvariantException
221
     * @throws \Throwable
222
     */
223
    protected function executeFields(
224
        ObjectType $objectType,
225
        $rootValue,
226
        $path,
227
        $fields
228
    ): array {
229
        $finalResults = [];
230
231
        foreach ($fields as $fieldName => $fieldNodes) {
232
            $fieldPath   = $path;
233
            $fieldPath[] = $fieldName;
234
235
            try {
236
                $result = $this->resolveField($objectType, $rootValue, $fieldNodes, $fieldPath);
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...
237
            } catch (UndefinedFieldException $ex) {
238
                continue;
239
            }
240
241
            $finalResults[$fieldName] = $result;
242
        }
243
244
        return $finalResults;
245
    }
246
247
    /**
248
     * Implements the "Evaluating selection sets" section of the spec for "write" mode.
249
     *
250
     * @param ObjectType $objectType
251
     * @param            $rootValue
252
     * @param            $path
253
     * @param            $fields
254
     * @return array
255
     * @throws InvalidTypeException
256
     * @throws \Digia\GraphQL\Error\ExecutionException
257
     * @throws \Digia\GraphQL\Error\InvariantException
258
     * @throws \Throwable
259
     */
260
    public function executeFieldsSerially(
261
        ObjectType $objectType,
262
        $rootValue,
263
        $path,
264
        $fields
265
    ) {
266
        //@TODO execute fields serially
267
        $finalResults = [];
268
269
        foreach ($fields as $fieldName => $fieldNodes) {
270
            $fieldPath   = $path;
271
            $fieldPath[] = $fieldName;
272
273
            try {
274
                $result = $this->resolveField($objectType, $rootValue, $fieldNodes, $fieldPath);
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...
275
            } catch (UndefinedFieldException $ex) {
276
                continue;
277
            }
278
279
            $finalResults[$fieldName] = $result;
280
        }
281
282
        return $finalResults;
283
    }
284
285
    /**
286
     * @param Schema     $schema
287
     * @param ObjectType $parentType
288
     * @param string     $fieldName
289
     * @return \Digia\GraphQL\Type\Definition\Field|null
290
     * @throws InvalidTypeException
291
     */
292
    public function getFieldDefinition(
293
        Schema $schema,
294
        ObjectType $parentType,
295
        string $fieldName
296
    ) {
297
        $schemaMetaFieldDefinition   = SchemaMetaFieldDefinition();
298
        $typeMetaFieldDefinition     = TypeMetaFieldDefinition();
299
        $typeNameMetaFieldDefinition = TypeNameMetaFieldDefinition();
300
301
        if ($fieldName === $schemaMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
302
            return $schemaMetaFieldDefinition;
303
        }
304
305
        if ($fieldName === $typeMetaFieldDefinition->getName() && $schema->getQuery() === $parentType) {
306
            return $typeMetaFieldDefinition;
307
        }
308
309
        if ($fieldName === $typeNameMetaFieldDefinition->getName()) {
310
            return $typeNameMetaFieldDefinition;
311
        }
312
313
        $fields = $parentType->getFields();
314
315
        return $fields[$fieldName] ?? null;
316
    }
317
318
319
    /**
320
     * @param ObjectType $parentType
321
     * @param            $rootValue
322
     * @param            $fieldNodes
323
     * @param            $path
324
     * @return array|null|string|\Throwable
325
     * @throws InvalidTypeException
326
     * @throws \Digia\GraphQL\Error\ExecutionException
327
     * @throws \Digia\GraphQL\Error\InvariantException
328
     * @throws \Throwable
329
     */
330
    protected function resolveField(
331
        ObjectType $parentType,
332
        $rootValue,
333
        $fieldNodes,
334
        $path
335
    ) {
336
        /** @var FieldNode $fieldNode */
337
        $fieldNode = $fieldNodes[0];
338
        $fieldName = $fieldNode->getNameValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldName is dead and can be removed.
Loading history...
339
340
        $field = $this->getFieldDefinition($this->context->getSchema(), $parentType, $fieldNode->getNameValue());
341
342
        if (null === $field) {
343
            throw new UndefinedFieldException('Undefined field definition.');
344
        }
345
346
        $info = $this->buildResolveInfo($fieldNodes, $fieldNode, $field, $parentType, $path, $this->context);
347
348
        $resolveFunction = $this->determineResolveFunction($field, $parentType, $this->context);
349
350
        $result = $this->resolveFieldValueOrError(
351
            $field,
352
            $fieldNode,
353
            $resolveFunction,
354
            $rootValue,
355
            $this->context,
356
            $info
357
        );
358
359
        $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...
360
            $field->getType(),
361
            $fieldNodes,
362
            $info,
363
            $path,
364
            $result// $result is passed as $source
365
        );
366
367
        return $result;
368
    }
369
370
    /**
371
     * @param array            $fieldNodes
372
     * @param FieldNode        $fieldNode
373
     * @param Field            $field
374
     * @param ObjectType       $parentType
375
     * @param                  $path
376
     * @param ExecutionContext $context
377
     * @return ResolveInfo
378
     */
379
    private function buildResolveInfo(
380
        array $fieldNodes,
381
        FieldNode $fieldNode,
382
        Field $field,
383
        ObjectType $parentType,
384
        $path,
385
        ExecutionContext $context
386
    ) {
387
        return new ResolveInfo([
388
            'fieldName'      => $fieldNode->getNameValue(),
389
            'fieldNodes'     => $fieldNodes,
390
            'returnType'     => $field->getType(),
391
            'parentType'     => $parentType,
392
            'path'           => $path,
393
            'schema'         => $context->getSchema(),
394
            'fragments'      => $context->getFragments(),
395
            'rootValue'      => $context->getRootValue(),
396
            'operation'      => $context->getOperation(),
397
            'variableValues' => $context->getVariableValues(),
398
        ]);
399
    }
400
401
    /**
402
     * @param Field            $field
403
     * @param ObjectType       $objectType
404
     * @param ExecutionContext $context
405
     * @return callable|mixed|null
406
     */
407
    private function determineResolveFunction(
408
        Field $field,
409
        ObjectType $objectType,
410
        ExecutionContext $context
411
    ) {
412
413
        if ($field->hasResolve()) {
414
            return $field->getResolve();
415
        }
416
417
        if ($objectType->hasResolve()) {
418
            return $objectType->getResolve();
419
        }
420
421
        return $this->context->getFieldResolver() ?? self::$defaultFieldResolver;
422
    }
423
424
    /**
425
     * @param TypeInterface $fieldType
426
     * @param               $fieldNodes
427
     * @param ResolveInfo   $info
428
     * @param               $path
429
     * @param               $result
430
     * @return null
431
     * @throws \Throwable
432
     */
433
    public function completeValueCatchingError(
434
        TypeInterface $fieldType,
435
        $fieldNodes,
436
        ResolveInfo $info,
437
        $path,
438
        &$result
439
    ) {
440
        if ($fieldType instanceof NonNullType) {
441
            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...
442
                $fieldType,
443
                $fieldNodes,
444
                $info,
445
                $path,
446
                $result
447
            );
448
        }
449
450
        try {
451
            $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...
452
                $fieldType,
453
                $fieldNodes,
454
                $info,
455
                $path,
456
                $result
457
            );
458
459
            return $completed;
460
        } catch (\Exception $ex) {
461
            $this->context->addError(new ExecutionException($ex->getMessage()));
462
            return null;
463
        }
464
    }
465
466
    /**
467
     * @param TypeInterface $fieldType
468
     * @param               $fieldNodes
469
     * @param ResolveInfo   $info
470
     * @param               $path
471
     * @param               $result
472
     * @throws \Throwable
473
     */
474
    public function completeValueWithLocatedError(
475
        TypeInterface $fieldType,
476
        $fieldNodes,
477
        ResolveInfo $info,
478
        $path,
479
        $result
480
    ) {
481
        try {
482
            $completed = $this->completeValue(
483
                $fieldType,
484
                $fieldNodes,
485
                $info,
486
                $path,
487
                $result
488
            );
489
            return $completed;
490
        } catch (\Exception $ex) {
491
            //@TODO throw located error
492
            throw $ex;
493
        } catch (\Throwable $ex) {
494
            //@TODO throw located error
495
            throw $ex;
496
        }
497
    }
498
499
    /**
500
     * @param TypeInterface $returnType
501
     * @param               $fieldNodes
502
     * @param ResolveInfo   $info
503
     * @param               $path
504
     * @param               $result
505
     * @return array|mixed
506
     * @throws ExecutionException
507
     * @throws \Throwable
508
     */
509
    private function completeValue(
510
        TypeInterface $returnType,
511
        $fieldNodes,
512
        ResolveInfo $info,
513
        $path,
514
        &$result
515
    ) {
516
        if ($result instanceof \Throwable) {
517
            throw $result;
518
        }
519
520
        // If result is null-like, return null.
521
        if (null === $result) {
522
            return null;
523
        }
524
525
        if ($returnType instanceof NonNullType) {
526
            $completed = $this->completeValue(
527
                $returnType->getOfType(),
528
                $fieldNodes,
529
                $info,
530
                $path,
531
                $result
532
            );
533
534
            if ($completed === null) {
535
                throw new ExecutionException(
536
                    sprintf(
537
                        'Cannot return null for non-nullable field %s.%s.',
538
                        $info->getParentType(), $info->getFieldName()
539
                    )
540
                );
541
            }
542
543
            return $completed;
544
        }
545
546
        // If field type is List, complete each item in the list with the inner type
547
        if ($returnType instanceof ListType) {
548
            return $this->completeListValue($returnType, $fieldNodes, $info, $path, $result);
549
        }
550
551
552
        // If field type is Scalar or Enum, serialize to a valid value, returning
553
        // null if serialization is not possible.
554
        if ($returnType instanceof LeafTypeInterface) {
555
            return $this->completeLeafValue($returnType, $result);
556
        }
557
558
        //@TODO Make a function for checking abstract type?
559
        if ($returnType instanceof InterfaceType || $returnType instanceof UnionType) {
560
            return $this->completeAbstractValue($returnType, $fieldNodes, $info, $path, $result);
561
        }
562
563
        // Field type must be Object, Interface or Union and expect sub-selections.
564
        if ($returnType instanceof ObjectType) {
565
            return $this->completeObjectValue($returnType, $fieldNodes, $info, $path, $result);
566
        }
567
568
        throw new ExecutionException("Cannot complete value of unexpected type \"{$returnType}\".");
569
    }
570
571
    /**
572
     * @param AbstractTypeInterface $returnType
573
     * @param                       $fieldNodes
574
     * @param ResolveInfo           $info
575
     * @param                       $path
576
     * @param                       $result
577
     * @return array
578
     * @throws ExecutionException
579
     * @throws InvalidTypeException
580
     * @throws \Digia\GraphQL\Error\InvariantException
581
     * @throws \Throwable
582
     */
583
    private function completeAbstractValue(
584
        AbstractTypeInterface $returnType,
585
        $fieldNodes,
586
        ResolveInfo $info,
587
        $path,
588
        &$result
589
    ) {
590
        $runtimeType = $returnType->resolveType($result, $this->context->getContextValue(), $info);
591
592
        if (null === $runtimeType) {
593
            //@TODO Show warning
594
            $runtimeType = $this->defaultTypeResolver($result, $this->context->getContextValue(), $info, $returnType);
595
        }
596
597
        $runtimeType = $this->ensureValidRuntimeType(
598
            $runtimeType,
599
            $returnType,
600
            $fieldNodes,
601
            $info,
602
            $result
603
        );
604
605
        return $this->completeObjectValue(
606
            $runtimeType,
607
            $fieldNodes,
608
            $info,
609
            $path,
610
            $result
611
        );
612
    }
613
614
    /**
615
     * @param                       $runtimeTypeOrName
616
     * @param AbstractTypeInterface $returnType
617
     * @param                       $fieldNodes
618
     * @param ResolveInfo           $info
619
     * @param                       $result
620
     * @return TypeInterface|null
621
     * @throws ExecutionException
622
     */
623
    private function ensureValidRuntimeType(
624
        $runtimeTypeOrName,
625
        AbstractTypeInterface $returnType,
626
        $fieldNodes,
627
        ResolveInfo $info,
628
        &$result
629
    ) {
630
        $runtimeType = is_string($runtimeTypeOrName)
631
            ? $this->context->getSchema()->getType($runtimeTypeOrName)
632
            : $runtimeTypeOrName;
633
634
        $runtimeTypeName = is_string($runtimeType) ?: $runtimeType->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on null. ( Ignorable by Annotation )

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

634
        $runtimeTypeName = is_string($runtimeType) ?: $runtimeType->/** @scrutinizer ignore-call */ getName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getName() does not exist on Digia\GraphQL\Type\Definition\TypeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Type\Defin...n\WrappingTypeInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

634
        $runtimeTypeName = is_string($runtimeType) ?: $runtimeType->/** @scrutinizer ignore-call */ getName();
Loading history...
635
        $returnTypeName  = $returnType->getName();
636
637
        if (!$runtimeType instanceof ObjectType) {
638
            $parentTypeName = $info->getParentType()->getName();
639
            $fieldName      = $info->getFieldName();
640
641
            throw new ExecutionException(
642
                "Abstract type {$returnTypeName} must resolve to an Object type at runtime " .
643
                "for field {$parentTypeName}.{$fieldName} with " .
644
                'value "' . $result . '", received "{$runtimeTypeName}".'
645
            );
646
        }
647
648
        if (!$this->context->getSchema()->isPossibleType($returnType, $runtimeType)) {
649
            throw new ExecutionException(
650
                "Runtime Object type \"{$runtimeTypeName}\" is not a possible type for \"{$returnTypeName}\"."
651
            );
652
        }
653
654
        if ($runtimeType !== $this->context->getSchema()->getType($runtimeType->getName())) {
655
            throw new ExecutionException(
656
                "Schema must contain unique named types but contains multiple types named \"{$runtimeTypeName}\". " .
657
                "Make sure that `resolveType` function of abstract type \"{$returnTypeName}\" returns the same " .
658
                "type instance as referenced anywhere else within the schema."
659
            );
660
        }
661
662
        return $runtimeType;
663
    }
664
665
    /**
666
     * @param                       $value
667
     * @param                       $context
668
     * @param ResolveInfo           $info
669
     * @param AbstractTypeInterface $abstractType
670
     * @return TypeInterface|null
671
     */
672
    private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractTypeInterface $abstractType)
673
    {
674
        $possibleTypes = $info->getSchema()->getPossibleTypes($abstractType);
675
676
        foreach ($possibleTypes as $type) {
677
            $isTypeOfResult = $type->isTypeOf($value, $context, $info);
678
679
            if (null !== $isTypeOfResult) {
680
                if ($isTypeOfResult) {
681
                    return $type;
682
                }
683
            }
684
        }
685
686
        return null;
687
    }
688
689
    /**
690
     * @param ListType    $returnType
691
     * @param             $fieldNodes
692
     * @param ResolveInfo $info
693
     * @param             $path
694
     * @param             $result
695
     * @return array
696
     * @throws \Throwable
697
     */
698
    private function completeListValue(
699
        ListType $returnType,
700
        $fieldNodes,
701
        ResolveInfo $info,
702
        $path,
703
        &$result
704
    ) {
705
        $itemType = $returnType->getOfType();
706
707
        $completedItems = [];
708
709
        foreach ($result as $key => $item) {
710
            $fieldPath        = $path;
711
            $fieldPath[]      = $key;
712
            $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...
713
            $completedItems[] = $completedItem;
714
        }
715
716
        return $completedItems;
717
    }
718
719
    /**
720
     * @param LeafTypeInterface $returnType
721
     * @param                   $result
722
     * @return mixed
723
     * @throws ExecutionException
724
     */
725
    private function completeLeafValue(LeafTypeInterface $returnType, &$result)
726
    {
727
        $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

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