Passed
Pull Request — master (#130)
by Quang
02:03
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\UndefinedException;
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 (UndefinedException $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 (UndefinedException $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|\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
339
        $field = $this->getFieldDefinition($this->context->getSchema(), $parentType, $fieldNode->getNameValue());
340
341
        if (null === $field) {
342
            throw new UndefinedException('Undefined field definition.');
343
        }
344
345
        $info = $this->buildResolveInfo($fieldNodes, $fieldNode, $field, $parentType, $path, $this->context);
346
347
        $resolveFunction = $this->determineResolveFunction($field, $parentType, $this->context);
348
349
        $result = $this->resolveFieldValueOrError(
350
            $field,
351
            $fieldNode,
352
            $resolveFunction,
353
            $rootValue,
354
            $this->context,
355
            $info
356
        );
357
358
        $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...
359
            $field->getType(),
360
            $fieldNodes,
361
            $info,
362
            $path,
363
            $result// $result is passed as $source
364
        );
365
366
        return $result;
367
    }
368
369
    /**
370
     * @param array            $fieldNodes
371
     * @param FieldNode        $fieldNode
372
     * @param Field            $field
373
     * @param ObjectType       $parentType
374
     * @param                  $path
375
     * @param ExecutionContext $context
376
     * @return ResolveInfo
377
     */
378
    private function buildResolveInfo(
379
        array $fieldNodes,
380
        FieldNode $fieldNode,
381
        Field $field,
382
        ObjectType $parentType,
383
        $path,
384
        ExecutionContext $context
385
    ) {
386
        return new ResolveInfo([
387
            'fieldName'      => $fieldNode->getNameValue(),
388
            'fieldNodes'     => $fieldNodes,
389
            'returnType'     => $field->getType(),
390
            'parentType'     => $parentType,
391
            'path'           => $path,
392
            'schema'         => $context->getSchema(),
393
            'fragments'      => $context->getFragments(),
394
            'rootValue'      => $context->getRootValue(),
395
            'operation'      => $context->getOperation(),
396
            'variableValues' => $context->getVariableValues(),
397
        ]);
398
    }
399
400
    /**
401
     * @param Field            $field
402
     * @param ObjectType       $objectType
403
     * @param ExecutionContext $context
404
     * @return callable|mixed|null
405
     */
406
    private function determineResolveFunction(
407
        Field $field,
408
        ObjectType $objectType,
409
        ExecutionContext $context
410
    ) {
411
412
        if ($field->hasResolve()) {
413
            return $field->getResolve();
414
        }
415
416
        if ($objectType->hasResolve()) {
417
            return $objectType->getResolve();
418
        }
419
420
        return $this->context->getFieldResolver() ?? self::$defaultFieldResolver;
421
    }
422
423
    /**
424
     * @param TypeInterface $fieldType
425
     * @param               $fieldNodes
426
     * @param ResolveInfo   $info
427
     * @param               $path
428
     * @param               $result
429
     * @return null
430
     * @throws \Throwable
431
     */
432
    public function completeValueCatchingError(
433
        TypeInterface $fieldType,
434
        $fieldNodes,
435
        ResolveInfo $info,
436
        $path,
437
        &$result
438
    ) {
439
        if ($fieldType instanceof NonNullType) {
440
            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...
441
                $fieldType,
442
                $fieldNodes,
443
                $info,
444
                $path,
445
                $result
446
            );
447
        }
448
449
        try {
450
            $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...
451
                $fieldType,
452
                $fieldNodes,
453
                $info,
454
                $path,
455
                $result
456
            );
457
458
            return $completed;
459
        } catch (\Exception $ex) {
460
            $this->context->addError(new ExecutionException($ex->getMessage()));
461
            return null;
462
        }
463
    }
464
465
    /**
466
     * @param TypeInterface $fieldType
467
     * @param               $fieldNodes
468
     * @param ResolveInfo   $info
469
     * @param               $path
470
     * @param               $result
471
     * @throws \Throwable
472
     */
473
    public function completeValueWithLocatedError(
474
        TypeInterface $fieldType,
475
        $fieldNodes,
476
        ResolveInfo $info,
477
        $path,
478
        $result
479
    ) {
480
        try {
481
            $completed = $this->completeValue(
482
                $fieldType,
483
                $fieldNodes,
484
                $info,
485
                $path,
486
                $result
487
            );
488
            return $completed;
489
        } catch (\Exception $ex) {
490
            //@TODO throw located error
491
            throw $ex;
492
        } catch (\Throwable $ex) {
493
            //@TODO throw located error
494
            throw $ex;
495
        }
496
    }
497
498
    /**
499
     * @param TypeInterface $returnType
500
     * @param               $fieldNodes
501
     * @param ResolveInfo   $info
502
     * @param               $path
503
     * @param               $result
504
     * @return array|mixed
505
     * @throws ExecutionException
506
     * @throws \Throwable
507
     */
508
    private function completeValue(
509
        TypeInterface $returnType,
510
        $fieldNodes,
511
        ResolveInfo $info,
512
        $path,
513
        &$result
514
    ) {
515
        if ($result instanceof \Throwable) {
516
            throw $result;
517
        }
518
519
        // If result is null-like, return null.
520
        if (null === $result) {
521
            return null;
522
        }
523
524
        if ($returnType instanceof NonNullType) {
525
            $completed = $this->completeValue(
526
                $returnType->getOfType(),
527
                $fieldNodes,
528
                $info,
529
                $path,
530
                $result
531
            );
532
533
            if ($completed === null) {
534
                throw new ExecutionException(
535
                    sprintf(
536
                        'Cannot return null for non-nullable field %s.%s.',
537
                        $info->getParentType(), $info->getFieldName()
538
                    )
539
                );
540
            }
541
542
            return $completed;
543
        }
544
545
        // If field type is List, complete each item in the list with the inner type
546
        if ($returnType instanceof ListType) {
547
            return $this->completeListValue($returnType, $fieldNodes, $info, $path, $result);
548
        }
549
550
551
        // If field type is Scalar or Enum, serialize to a valid value, returning
552
        // null if serialization is not possible.
553
        if ($returnType instanceof LeafTypeInterface) {
554
            return $this->completeLeafValue($returnType, $result);
555
        }
556
557
        //@TODO Make a function for checking abstract type?
558
        if ($returnType instanceof InterfaceType || $returnType instanceof UnionType) {
559
            return $this->completeAbstractValue($returnType, $fieldNodes, $info, $path, $result);
560
        }
561
562
        // Field type must be Object, Interface or Union and expect sub-selections.
563
        if ($returnType instanceof ObjectType) {
564
            return $this->completeObjectValue($returnType, $fieldNodes, $info, $path, $result);
565
        }
566
567
        throw new ExecutionException("Cannot complete value of unexpected type \"{$returnType}\".");
568
    }
569
570
    /**
571
     * @param AbstractTypeInterface $returnType
572
     * @param                       $fieldNodes
573
     * @param ResolveInfo           $info
574
     * @param                       $path
575
     * @param                       $result
576
     * @return array
577
     * @throws ExecutionException
578
     * @throws InvalidTypeException
579
     * @throws \Digia\GraphQL\Error\InvariantException
580
     * @throws \Throwable
581
     */
582
    private function completeAbstractValue(
583
        AbstractTypeInterface $returnType,
584
        $fieldNodes,
585
        ResolveInfo $info,
586
        $path,
587
        &$result
588
    ) {
589
        $runtimeType = $returnType->resolveType($result, $this->context->getContextValue(), $info);
590
591
        if (null === $runtimeType) {
592
            //@TODO Show warning
593
            $runtimeType = $this->defaultTypeResolver($result, $this->context->getContextValue(), $info, $returnType);
594
        }
595
596
        $runtimeType = $this->ensureValidRuntimeType(
597
            $runtimeType,
598
            $returnType,
599
            $fieldNodes,
600
            $info,
601
            $result
602
        );
603
604
        return $this->completeObjectValue(
605
            $runtimeType,
606
            $fieldNodes,
607
            $info,
608
            $path,
609
            $result
610
        );
611
    }
612
613
    /**
614
     * @param                       $runtimeTypeOrName
615
     * @param AbstractTypeInterface $returnType
616
     * @param                       $fieldNodes
617
     * @param ResolveInfo           $info
618
     * @param                       $result
619
     * @return TypeInterface|null
620
     * @throws ExecutionException
621
     */
622
    private function ensureValidRuntimeType(
623
        $runtimeTypeOrName,
624
        AbstractTypeInterface $returnType,
625
        $fieldNodes,
626
        ResolveInfo $info,
627
        &$result
628
    ) {
629
        $runtimeType = is_string($runtimeTypeOrName)
630
            ? $this->context->getSchema()->getType($runtimeTypeOrName)
631
            : $runtimeTypeOrName;
632
633
        $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

633
        $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

633
        $runtimeTypeName = is_string($runtimeType) ?: $runtimeType->/** @scrutinizer ignore-call */ getName();
Loading history...
634
        $returnTypeName  = $returnType->getName();
635
636
        if (!$runtimeType instanceof ObjectType) {
637
            $parentTypeName = $info->getParentType()->getName();
638
            $fieldName      = $info->getFieldName();
639
640
            throw new ExecutionException(
641
                "Abstract type {$returnTypeName} must resolve to an Object type at runtime " .
642
                "for field {$parentTypeName}.{$fieldName} with " .
643
                'value "' . $result . '", received "{$runtimeTypeName}".'
644
            );
645
        }
646
647
        if (!$this->context->getSchema()->isPossibleType($returnType, $runtimeType)) {
648
            throw new ExecutionException(
649
                "Runtime Object type \"{$runtimeTypeName}\" is not a possible type for \"{$returnTypeName}\"."
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 \"{$runtimeTypeName}\". " .
656
                "Make sure that `resolveType` function of abstract type \"{$returnTypeName}\" 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 $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, $getter)) {
812
                $property = $rootValue->{$getter}();
813
            } else {
814
                if (property_exists($rootValue, $fieldName)) {
815
                    $property = $rootValue->{$fieldName};
816
                }
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