Passed
Push — master ( 89369f...5a90e9 )
by Vladimir
03:51
created

Executor::completeValue()   C

Complexity

Conditions 13
Paths 12

Size

Total Lines 79
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 13.0033

Importance

Changes 0
Metric Value
dl 0
loc 79
ccs 36
cts 37
cp 0.973
rs 5.1136
c 0
b 0
f 0
cc 13
eloc 36
nc 12
nop 5
crap 13.0033

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace GraphQL\Executor;
3
4
use GraphQL\Error\Error;
5
use GraphQL\Error\InvariantViolation;
6
use GraphQL\Error\Warning;
7
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
8
use GraphQL\Executor\Promise\Promise;
9
use GraphQL\Language\AST\DocumentNode;
10
use GraphQL\Language\AST\FieldNode;
11
use GraphQL\Language\AST\FragmentDefinitionNode;
12
use GraphQL\Language\AST\FragmentSpreadNode;
13
use GraphQL\Language\AST\InlineFragmentNode;
14
use GraphQL\Language\AST\NodeKind;
15
use GraphQL\Language\AST\OperationDefinitionNode;
16
use GraphQL\Language\AST\SelectionSetNode;
17
use GraphQL\Executor\Promise\PromiseAdapter;
18
use GraphQL\Type\Schema;
19
use GraphQL\Type\Definition\AbstractType;
20
use GraphQL\Type\Definition\Directive;
21
use GraphQL\Type\Definition\FieldDefinition;
22
use GraphQL\Type\Definition\InterfaceType;
23
use GraphQL\Type\Definition\LeafType;
24
use GraphQL\Type\Definition\ListOfType;
25
use GraphQL\Type\Definition\NonNull;
26
use GraphQL\Type\Definition\ObjectType;
27
use GraphQL\Type\Definition\ResolveInfo;
28
use GraphQL\Type\Definition\Type;
29
use GraphQL\Type\Introspection;
30
use GraphQL\Utils\TypeInfo;
31
use GraphQL\Utils\Utils;
32
33
/**
34
 * Implements the "Evaluating requests" section of the GraphQL specification.
35
 */
36
class Executor
37
{
38
    private static $UNDEFINED;
39
40
    private static $defaultFieldResolver = [__CLASS__, 'defaultFieldResolver'];
41
42
    /**
43
     * @var PromiseAdapter
44
     */
45
    private static $promiseAdapter;
46
47
    /**
48
     * @param PromiseAdapter|null $promiseAdapter
49
     */
50 24
    public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
51
    {
52 24
        self::$promiseAdapter = $promiseAdapter;
53 24
    }
54
55
    /**
56
     * @return PromiseAdapter
57
     */
58 143
    public static function getPromiseAdapter()
59
    {
60 143
        return self::$promiseAdapter ?: (self::$promiseAdapter = new SyncPromiseAdapter());
61
    }
62
63
    /**
64
     * Custom default resolve function
65
     *
66
     * @param $fn
67
     * @throws \Exception
68
     */
69
    public static function setDefaultFieldResolver(callable $fn)
70
    {
71
        self::$defaultFieldResolver = $fn;
72
    }
73
74
    /**
75
     * Executes DocumentNode against given $schema.
76
     *
77
     * Always returns ExecutionResult and never throws. All errors which occur during operation
78
     * execution are collected in `$result->errors`.
79
     *
80
     * @api
81
     * @param Schema $schema
82
     * @param DocumentNode $ast
83
     * @param $rootValue
84
     * @param $contextValue
85
     * @param array|\ArrayAccess $variableValues
86
     * @param null $operationName
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $operationName is correct as it would always require null to be passed?
Loading history...
87
     * @param callable $fieldResolver
88
     *
89
     * @return ExecutionResult|Promise
90
     */
91 116
    public static function execute(
92
        Schema $schema,
93
        DocumentNode $ast,
94
        $rootValue = null,
95
        $contextValue = null,
96
        $variableValues = null,
97
        $operationName = null,
98
        callable $fieldResolver = null
99
    )
100
    {
101
        // TODO: deprecate (just always use SyncAdapter here) and have `promiseToExecute()` for other cases
102 116
        $promiseAdapter = self::getPromiseAdapter();
103 116
        $result = self::promiseToExecute(
104 116
            $promiseAdapter,
105 116
            $schema,
106 116
            $ast,
107 116
            $rootValue,
108 116
            $contextValue,
109 116
            $variableValues,
110 116
            $operationName,
111 116
            $fieldResolver
112
        );
113
114
        // Wait for promised results when using sync promises
115 116
        if ($promiseAdapter instanceof SyncPromiseAdapter) {
116 116
            $result = $promiseAdapter->wait($result);
117
        }
118
119 116
        return $result;
120
    }
121
122
    /**
123
     * Same as execute(), but requires promise adapter and returns a promise which is always
124
     * fulfilled with an instance of ExecutionResult and never rejected.
125
     *
126
     * Useful for async PHP platforms.
127
     *
128
     * @api
129
     * @param PromiseAdapter $promiseAdapter
130
     * @param Schema $schema
131
     * @param DocumentNode $ast
132
     * @param null $rootValue
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $rootValue is correct as it would always require null to be passed?
Loading history...
133
     * @param null $contextValue
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $contextValue is correct as it would always require null to be passed?
Loading history...
134
     * @param null $variableValues
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $variableValues is correct as it would always require null to be passed?
Loading history...
135
     * @param null $operationName
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $operationName is correct as it would always require null to be passed?
Loading history...
136
     * @param callable|null $fieldResolver
137
     * @return Promise
138
     */
139 200
    public static function promiseToExecute(
140
        PromiseAdapter $promiseAdapter,
141
        Schema $schema,
142
        DocumentNode $ast,
143
        $rootValue = null,
144
        $contextValue = null,
145
        $variableValues = null,
146
        $operationName = null,
147
        callable $fieldResolver = null
148
    )
149
    {
150 200
        $exeContext = self::buildExecutionContext(
151 200
            $schema,
152 200
            $ast,
153 200
            $rootValue,
154 200
            $contextValue,
155 200
            $variableValues,
156 200
            $operationName,
157 200
            $fieldResolver,
158 200
            $promiseAdapter
159
        );
160
161 200
        if (is_array($exeContext)) {
162 14
            return $promiseAdapter->createFulfilled(new ExecutionResult(null, $exeContext));
163
        }
164
165 187
        $executor = new self($exeContext);
166 187
        return $executor->doExecute();
167
    }
168
169
    /**
170
     * Constructs an ExecutionContext object from the arguments passed to
171
     * execute, which we will pass throughout the other execution methods.
172
     *
173
     * @param Schema $schema
174
     * @param DocumentNode $documentNode
175
     * @param $rootValue
176
     * @param $contextValue
177
     * @param array|\Traversable $rawVariableValues
178
     * @param string $operationName
179
     * @param callable $fieldResolver
180
     * @param PromiseAdapter $promiseAdapter
181
     *
182
     * @return ExecutionContext|Error[]
183
     */
184 200
    private static function buildExecutionContext(
185
        Schema $schema,
186
        DocumentNode $documentNode,
187
        $rootValue,
188
        $contextValue,
189
        $rawVariableValues,
190
        $operationName = null,
191
        callable $fieldResolver = null,
192
        PromiseAdapter $promiseAdapter = null
193
    )
194
    {
195 200
        $errors = [];
196 200
        $fragments = [];
197
        /** @var OperationDefinitionNode $operation */
198 200
        $operation = null;
199 200
        $hasMultipleAssumedOperations = false;
200
201 200
        foreach ($documentNode->definitions as $definition) {
202 200
            switch ($definition->kind) {
0 ignored issues
show
Bug introduced by
Accessing kind on the interface GraphQL\Language\AST\DefinitionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
203 200
                case NodeKind::OPERATION_DEFINITION:
204 199
                    if (!$operationName && $operation) {
205 1
                        $hasMultipleAssumedOperations = true;
206
                    }
207 199
                    if (!$operationName ||
208 199
                        (isset($definition->name) && $definition->name->value === $operationName)) {
0 ignored issues
show
Bug introduced by
Accessing name on the interface GraphQL\Language\AST\DefinitionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
209 198
                        $operation = $definition;
210
                    }
211 199
                    break;
212 14
                case NodeKind::FRAGMENT_DEFINITION:
213 13
                    $fragments[$definition->name->value] = $definition;
214 200
                    break;
215
            }
216
        }
217
218 200
        if (!$operation) {
219 2
            if ($operationName) {
220 1
                $errors[] = new Error("Unknown operation named \"$operationName\".");
221
            } else {
222 2
                $errors[] = new Error('Must provide an operation.');
223
            }
224 198
        } else if ($hasMultipleAssumedOperations) {
225 1
            $errors[] = new Error(
226 1
                'Must provide operation name if query contains multiple operations.'
227
            );
228
229
        }
230
231 200
        $variableValues = null;
232 200
        if ($operation) {
233 198
            $coercedVariableValues = Values::getVariableValues(
234 198
                $schema,
235 198
                $operation->variableDefinitions ?: [],
236 198
                $rawVariableValues ?: []
0 ignored issues
show
Bug introduced by
It seems like $rawVariableValues ?: array() can also be of type Traversable; however, parameter $inputs of GraphQL\Executor\Values::getVariableValues() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

236
                /** @scrutinizer ignore-type */ $rawVariableValues ?: []
Loading history...
237
            );
238
239 198
            if ($coercedVariableValues['errors']) {
240 11
                $errors = array_merge($errors, $coercedVariableValues['errors']);
241
            } else {
242 188
                $variableValues = $coercedVariableValues['coerced'];
243
            }
244
        }
245
246 200
        if ($errors) {
247 14
            return $errors;
248
        }
249
250 187
        Utils::invariant($operation, 'Has operation if no errors.');
251 187
        Utils::invariant($variableValues !== null, 'Has variables if no errors.');
252
253 187
        return new ExecutionContext(
254 187
            $schema,
255 187
            $fragments,
256 187
            $rootValue,
257 187
            $contextValue,
258 187
            $operation,
259 187
            $variableValues,
260 187
            $errors,
261 187
            $fieldResolver ?: self::$defaultFieldResolver,
262 187
            $promiseAdapter ?: self::getPromiseAdapter()
263
        );
264
    }
265
266
    /**
267
     * @var ExecutionContext
268
     */
269
    private $exeContext;
270
271
    /**
272
     * @var PromiseAdapter
273
     */
274
    private $promises;
275
276
    /**
277
     * Executor constructor.
278
     *
279
     * @param ExecutionContext $context
280
     */
281 187
    private function __construct(ExecutionContext $context)
282
    {
283 187
        if (!self::$UNDEFINED) {
284 1
            self::$UNDEFINED = Utils::undefined();
285
        }
286
287 187
        $this->exeContext = $context;
288 187
    }
289
290
    /**
291
     * @return Promise
292
     */
293 187
    private function doExecute()
294
    {
295
        // Return a Promise that will eventually resolve to the data described by
296
        // The "Response" section of the GraphQL specification.
297
        //
298
        // If errors are encountered while executing a GraphQL field, only that
299
        // field and its descendants will be omitted, and sibling fields will still
300
        // be executed. An execution which encounters errors will still result in a
301
        // resolved Promise.
302
        $result = $this->exeContext->promises->create(function (callable $resolve) {
303 187
            return $resolve($this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue));
304 187
        });
305
        return $result
306
            ->then(null, function ($error) {
307
                // Errors from sub-fields of a NonNull type may propagate to the top level,
308
                // at which point we still log the error and null the parent field, which
309
                // in this case is the entire response.
310
                $this->exeContext->addError($error);
311
                return null;
312 187
            })
313
            ->then(function ($data) {
314 187
                if ($data !== null){
315 183
                    $data = (array) $data;
316
                }
317 187
                return new ExecutionResult($data, $this->exeContext->errors);
318 187
            });
319
    }
320
321
    /**
322
     * Implements the "Evaluating operations" section of the spec.
323
     *
324
     * @param OperationDefinitionNode $operation
325
     * @param $rootValue
326
     * @return Promise|\stdClass|array
327
     */
328 187
    private function executeOperation(OperationDefinitionNode $operation, $rootValue)
329
    {
330 187
        $type = $this->getOperationRootType($this->exeContext->schema, $operation);
331 187
        $fields = $this->collectFields($type, $operation->selectionSet, new \ArrayObject(), new \ArrayObject());
332
333 187
        $path = [];
334
335
        // Errors from sub-fields of a NonNull type may propagate to the top level,
336
        // at which point we still log the error and null the parent field, which
337
        // in this case is the entire response.
338
        //
339
        // Similar to completeValueCatchingError.
340
        try {
341 187
            $result = $operation->operation === 'mutation' ?
342 5
                $this->executeFieldsSerially($type, $rootValue, $path, $fields) :
343 187
                $this->executeFields($type, $rootValue, $path, $fields);
344
345 185
            $promise = $this->getPromise($result);
346 185
            if ($promise) {
347
                return $promise->then(null, function($error) {
348 2
                    $this->exeContext->addError($error);
349 2
                    return null;
350 41
                });
351
            }
352 145
            return $result;
353
354 2
        } catch (Error $error) {
355 2
            $this->exeContext->addError($error);
356 2
            return null;
357
        }
358
    }
359
360
    /**
361
     * Extracts the root type of the operation from the schema.
362
     *
363
     * @param Schema $schema
364
     * @param OperationDefinitionNode $operation
365
     * @return ObjectType
366
     * @throws Error
367
     */
368 187
    private function getOperationRootType(Schema $schema, OperationDefinitionNode $operation)
369
    {
370 187
        switch ($operation->operation) {
371 187
            case 'query':
372 180
                $queryType = $schema->getQueryType();
373 180
                if (!$queryType) {
0 ignored issues
show
introduced by
$queryType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
374
                    throw new Error(
375
                        'Schema does not define the required query root type.',
376
                        [$operation]
377
                    );
378
                }
379 180
                return $queryType;
380 7
            case 'mutation':
381 5
                $mutationType = $schema->getMutationType();
382 5
                if (!$mutationType) {
0 ignored issues
show
introduced by
$mutationType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
383
                    throw new Error(
384
                        'Schema is not configured for mutations.',
385
                        [$operation]
386
                    );
387
                }
388 5
                return $mutationType;
389 2
            case 'subscription':
390 2
                $subscriptionType = $schema->getSubscriptionType();
391 2
                if (!$subscriptionType) {
0 ignored issues
show
introduced by
$subscriptionType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
392
                    throw new Error(
393
                        'Schema is not configured for subscriptions.',
394
                        [ $operation ]
395
                    );
396
                }
397 2
                return $subscriptionType;
398
            default:
399
                throw new Error(
400
                    'Can only execute queries, mutations and subscriptions.',
401
                    [$operation]
402
                );
403
        }
404
    }
405
406
    /**
407
     * Implements the "Evaluating selection sets" section of the spec
408
     * for "write" mode.
409
     *
410
     * @param ObjectType $parentType
411
     * @param $sourceValue
412
     * @param $path
413
     * @param $fields
414
     * @return Promise|\stdClass|array
415
     */
416 5
    private function executeFieldsSerially(ObjectType $parentType, $sourceValue, $path, $fields)
417
    {
418 5
        $prevPromise = $this->exeContext->promises->createFulfilled([]);
419
420
        $process = function ($results, $responseName, $path, $parentType, $sourceValue, $fieldNodes) {
421 5
            $fieldPath = $path;
422 5
            $fieldPath[] = $responseName;
423 5
            $result = $this->resolveField($parentType, $sourceValue, $fieldNodes, $fieldPath);
424 5
            if ($result === self::$UNDEFINED) {
425 1
                return $results;
426
            }
427 4
            $promise = $this->getPromise($result);
428 4
            if ($promise) {
429
                return $promise->then(function ($resolvedResult) use ($responseName, $results) {
430 2
                    $results[$responseName] = $resolvedResult;
431 2
                    return $results;
432 2
                });
433
            }
434 4
            $results[$responseName] = $result;
435 4
            return $results;
436 5
        };
437
438 5
        foreach ($fields as $responseName => $fieldNodes) {
439
            $prevPromise = $prevPromise->then(function ($resolvedResults) use ($process, $responseName, $path, $parentType, $sourceValue, $fieldNodes) {
440 5
                return $process($resolvedResults, $responseName, $path, $parentType, $sourceValue, $fieldNodes);
441 5
            });
442
        }
443
444
        return $prevPromise->then(function ($resolvedResults) {
445 5
            return self::fixResultsIfEmptyArray($resolvedResults);
446 5
        });
447
    }
448
449
    /**
450
     * Implements the "Evaluating selection sets" section of the spec
451
     * for "read" mode.
452
     *
453
     * @param ObjectType $parentType
454
     * @param $source
455
     * @param $path
456
     * @param $fields
457
     * @return Promise|\stdClass|array
458
     */
459 184
    private function executeFields(ObjectType $parentType, $source, $path, $fields)
460
    {
461 184
        $containsPromise = false;
462 184
        $finalResults = [];
463
464 184
        foreach ($fields as $responseName => $fieldNodes) {
465 184
            $fieldPath = $path;
466 184
            $fieldPath[] = $responseName;
467 184
            $result = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath);
468 182
            if ($result === self::$UNDEFINED) {
469 6
                continue;
470
            }
471 179
            if (!$containsPromise && $this->getPromise($result)) {
472 36
                $containsPromise = true;
473
            }
474 179
            $finalResults[$responseName] = $result;
475
        }
476
477
        // If there are no promises, we can just return the object
478 182
        if (!$containsPromise) {
479 155
            return self::fixResultsIfEmptyArray($finalResults);
480
        }
481
482
        // Otherwise, results is a map from field name to the result
483
        // of resolving that field, which is possibly a promise. Return
484
        // a promise that will return this same map, but with any
485
        // promises replaced with the values they resolved to.
486 36
        return $this->promiseForAssocArray($finalResults);
487
    }
488
489
    /**
490
     * This function transforms a PHP `array<string, Promise|scalar|array>` into
491
     * a `Promise<array<key,scalar|array>>`
492
     *
493
     * In other words it returns a promise which resolves to normal PHP associative array which doesn't contain
494
     * any promises.
495
     *
496
     * @param array $assoc
497
     * @return mixed
498
     */
499 36
    private function promiseForAssocArray(array $assoc)
500
    {
501 36
        $keys = array_keys($assoc);
502 36
        $valuesAndPromises = array_values($assoc);
503
504 36
        $promise = $this->exeContext->promises->all($valuesAndPromises);
505
506
        return $promise->then(function($values) use ($keys) {
507 34
            $resolvedResults = [];
508 34
            foreach ($values as $i => $value) {
509 34
                $resolvedResults[$keys[$i]] = $value;
510
            }
511 34
            return self::fixResultsIfEmptyArray($resolvedResults);
512 36
        });
513
    }
514
515
    /**
516
     * @see https://github.com/webonyx/graphql-php/issues/59
517
     *
518
     * @param $results
519
     * @return \stdClass|array
520
     */
521 183
    private static function fixResultsIfEmptyArray($results)
522
    {
523 183
        if ([] === $results) {
524 5
            $results = new \stdClass();
525
        }
526
527 183
        return $results;
528
    }
529
530
    /**
531
     * Given a selectionSet, adds all of the fields in that selection to
532
     * the passed in map of fields, and returns it at the end.
533
     *
534
     * CollectFields requires the "runtime type" of an object. For a field which
535
     * returns an Interface or Union type, the "runtime type" will be the actual
536
     * Object type returned by that field.
537
     *
538
     * @param ObjectType $runtimeType
539
     * @param SelectionSetNode $selectionSet
540
     * @param $fields
541
     * @param $visitedFragmentNames
542
     *
543
     * @return \ArrayObject
544
     */
545 187
    private function collectFields(
546
        ObjectType $runtimeType,
547
        SelectionSetNode $selectionSet,
548
        $fields,
549
        $visitedFragmentNames
550
    )
551
    {
552 187
        $exeContext = $this->exeContext;
553 187
        foreach ($selectionSet->selections as $selection) {
554 187
            switch ($selection->kind) {
0 ignored issues
show
Bug introduced by
Accessing kind on the interface GraphQL\Language\AST\SelectionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
555 187
                case NodeKind::FIELD:
556 187
                    if (!$this->shouldIncludeNode($selection)) {
557 2
                        continue;
558
                    }
559 187
                    $name = self::getFieldEntryKey($selection);
560 187
                    if (!isset($fields[$name])) {
561 187
                        $fields[$name] = new \ArrayObject();
562
                    }
563 187
                    $fields[$name][] = $selection;
564 187
                    break;
565 29
                case NodeKind::INLINE_FRAGMENT:
566 21
                    if (!$this->shouldIncludeNode($selection) ||
567 21
                        !$this->doesFragmentConditionMatch($selection, $runtimeType)
568
                    ) {
569 19
                        continue;
570
                    }
571 21
                    $this->collectFields(
572 21
                        $runtimeType,
573 21
                        $selection->selectionSet,
0 ignored issues
show
Bug introduced by
Accessing selectionSet on the interface GraphQL\Language\AST\SelectionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
574 21
                        $fields,
575 21
                        $visitedFragmentNames
576
                    );
577 21
                    break;
578 10
                case NodeKind::FRAGMENT_SPREAD:
579 10
                    $fragName = $selection->name->value;
0 ignored issues
show
Bug introduced by
Accessing name on the interface GraphQL\Language\AST\SelectionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
580 10
                    if (!empty($visitedFragmentNames[$fragName]) || !$this->shouldIncludeNode($selection)) {
581 2
                        continue;
582
                    }
583 10
                    $visitedFragmentNames[$fragName] = true;
584
585
                    /** @var FragmentDefinitionNode|null $fragment */
586 10
                    $fragment = isset($exeContext->fragments[$fragName]) ? $exeContext->fragments[$fragName] : null;
587 10
                    if (!$fragment || !$this->doesFragmentConditionMatch($fragment, $runtimeType)) {
588 1
                        continue;
589
                    }
590 9
                    $this->collectFields(
591 9
                        $runtimeType,
592 9
                        $fragment->selectionSet,
593 9
                        $fields,
594 9
                        $visitedFragmentNames
595
                    );
596 187
                    break;
597
            }
598
        }
599 187
        return $fields;
600
    }
601
602
    /**
603
     * Determines if a field should be included based on the @include and @skip
604
     * directives, where @skip has higher precedence than @include.
605
     *
606
     * @param FragmentSpreadNode | FieldNode | InlineFragmentNode $node
607
     * @return bool
608
     */
609 187
    private function shouldIncludeNode($node)
610
    {
611 187
        $variableValues = $this->exeContext->variableValues;
612 187
        $skipDirective = Directive::skipDirective();
613
614 187
        $skip = Values::getDirectiveValues(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $skip is correct as GraphQL\Executor\Values:...$node, $variableValues) targeting GraphQL\Executor\Values::getDirectiveValues() 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...
615 187
            $skipDirective,
616 187
            $node,
617 187
            $variableValues
618
        );
619
620 187
        if (isset($skip['if']) && $skip['if'] === true) {
621 5
            return false;
622
        }
623
624 187
        $includeDirective = Directive::includeDirective();
625
626 187
        $include = Values::getDirectiveValues(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $include is correct as GraphQL\Executor\Values:...$node, $variableValues) targeting GraphQL\Executor\Values::getDirectiveValues() 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...
627 187
            $includeDirective,
628 187
            $node,
629 187
            $variableValues
630
        );
631
632 187
        if (isset($include['if']) && $include['if'] === false) {
633 5
            return false;
634
        }
635 187
        return true;
636
    }
637
638
    /**
639
     * Determines if a fragment is applicable to the given type.
640
     *
641
     * @param $fragment
642
     * @param ObjectType $type
643
     * @return bool
644
     */
645 29
    private function doesFragmentConditionMatch(/* FragmentDefinitionNode | InlineFragmentNode*/ $fragment, ObjectType $type)
646
    {
647 29
        $typeConditionNode = $fragment->typeCondition;
648
649 29
        if (!$typeConditionNode) {
650 1
            return true;
651
        }
652
653 28
        $conditionalType = TypeInfo::typeFromAST($this->exeContext->schema, $typeConditionNode);
654 28
        if ($conditionalType === $type) {
0 ignored issues
show
introduced by
The condition $conditionalType === $type is always false.
Loading history...
655 27
            return true;
656
        }
657 18
        if ($conditionalType instanceof AbstractType) {
658 1
            return $this->exeContext->schema->isPossibleType($conditionalType, $type);
659
        }
660 18
        return false;
661
    }
662
663
    /**
664
     * Implements the logic to compute the key of a given fields entry
665
     *
666
     * @param FieldNode $node
667
     * @return string
668
     */
669 187
    private static function getFieldEntryKey(FieldNode $node)
670
    {
671 187
        return $node->alias ? $node->alias->value : $node->name->value;
672
    }
673
674
    /**
675
     * Resolves the field on the given source object. In particular, this
676
     * figures out the value that the field returns by calling its resolve function,
677
     * then calls completeValue to complete promises, serialize scalars, or execute
678
     * the sub-selection-set for objects.
679
     *
680
     * @param ObjectType $parentType
681
     * @param $source
682
     * @param $fieldNodes
683
     * @param $path
684
     *
685
     * @return array|\Exception|mixed|null
686
     */
687 187
    private function resolveField(ObjectType $parentType, $source, $fieldNodes, $path)
688
    {
689 187
        $exeContext = $this->exeContext;
690 187
        $fieldNode = $fieldNodes[0];
691
692 187
        $fieldName = $fieldNode->name->value;
693 187
        $fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName);
694
695 187
        if (!$fieldDef) {
0 ignored issues
show
introduced by
$fieldDef is of type GraphQL\Type\Definition\FieldDefinition, thus it always evaluated to true.
Loading history...
696 7
            return self::$UNDEFINED;
697
        }
698
699 183
        $returnType = $fieldDef->getType();
700
701
        // The resolve function's optional third argument is a collection of
702
        // information about the current execution state.
703 183
        $info = new ResolveInfo([
704 183
            'fieldName' => $fieldName,
705 183
            'fieldNodes' => $fieldNodes,
706 183
            'returnType' => $returnType,
707 183
            'parentType' => $parentType,
708 183
            'path' => $path,
709 183
            'schema' => $exeContext->schema,
710 183
            'fragments' => $exeContext->fragments,
711 183
            'rootValue' => $exeContext->rootValue,
712 183
            'operation' => $exeContext->operation,
713 183
            'variableValues' => $exeContext->variableValues,
714
        ]);
715
716
717 183
        if (isset($fieldDef->resolveFn)) {
718 132
            $resolveFn = $fieldDef->resolveFn;
719 116
        } else if (isset($parentType->resolveFieldFn)) {
720
            $resolveFn = $parentType->resolveFieldFn;
721
        } else {
722 116
            $resolveFn = $this->exeContext->fieldResolver;
723
        }
724
725
        // The resolve function's optional third argument is a context value that
726
        // is provided to every resolve function within an execution. It is commonly
727
        // used to represent an authenticated user, or request-specific caches.
728 183
        $context = $exeContext->contextValue;
729
730
        // Get the resolve function, regardless of if its result is normal
731
        // or abrupt (error).
732 183
        $result = $this->resolveOrError(
733 183
            $fieldDef,
734 183
            $fieldNode,
735 183
            $resolveFn,
736 183
            $source,
737 183
            $context,
738 183
            $info
739
        );
740
741 183
        $result = $this->completeValueCatchingError(
742 183
            $returnType,
743 183
            $fieldNodes,
744 183
            $info,
745 183
            $path,
746 183
            $result
747
        );
748
749 181
        return $result;
750
    }
751
752
    /**
753
     * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
754
     * function. Returns the result of resolveFn or the abrupt-return Error object.
755
     *
756
     * @param FieldDefinition $fieldDef
757
     * @param FieldNode $fieldNode
758
     * @param callable $resolveFn
759
     * @param mixed $source
760
     * @param mixed $context
761
     * @param ResolveInfo $info
762
     * @return \Throwable|Promise|mixed
763
     */
764 183
    private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $context, $info)
765
    {
766
        try {
767
            // Build hash of arguments from the field.arguments AST, using the
768
            // variables scope to fulfill any variable references.
769 183
            $args = Values::getArgumentValues(
770 183
                $fieldDef,
771 183
                $fieldNode,
772 183
                $this->exeContext->variableValues
773
            );
774
775 180
            return $resolveFn($source, $args, $context, $info);
776 16
        } catch (\Exception $error) {
777 16
            return $error;
778
        } catch (\Throwable $error) {
779
            return $error;
780
        }
781
    }
782
783
    /**
784
     * This is a small wrapper around completeValue which detects and logs errors
785
     * in the execution context.
786
     *
787
     * @param Type $returnType
788
     * @param $fieldNodes
789
     * @param ResolveInfo $info
790
     * @param $path
791
     * @param $result
792
     * @return array|null|Promise
793
     */
794 183
    private function completeValueCatchingError(
795
        Type $returnType,
796
        $fieldNodes,
797
        ResolveInfo $info,
798
        $path,
799
        $result
800
    )
801
    {
802 183
        $exeContext = $this->exeContext;
803
804
        // If the field type is non-nullable, then it is resolved without any
805
        // protection from errors.
806 183
        if ($returnType instanceof NonNull) {
807 50
            return $this->completeValueWithLocatedError(
808 50
                $returnType,
809 50
                $fieldNodes,
810 50
                $info,
811 50
                $path,
812 50
                $result
813
            );
814
        }
815
816
        // Otherwise, error protection is applied, logging the error and resolving
817
        // a null value for this field if one is encountered.
818
        try {
819 179
            $completed = $this->completeValueWithLocatedError(
820 179
                $returnType,
821 179
                $fieldNodes,
822 179
                $info,
823 179
                $path,
824 179
                $result
825
            );
826
827 168
            $promise = $this->getPromise($completed);
828 168
            if ($promise) {
829
                return $promise->then(null, function ($error) use ($exeContext) {
830 24
                    $exeContext->addError($error);
831 24
                    return $this->exeContext->promises->createFulfilled(null);
832 36
                });
833
            }
834 148
            return $completed;
835 27
        } catch (Error $err) {
836
            // If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
837
            // and return null.
838 27
            $exeContext->addError($err);
839 27
            return null;
840
        }
841
    }
842
843
844
    /**
845
     * This is a small wrapper around completeValue which annotates errors with
846
     * location information.
847
     *
848
     * @param Type $returnType
849
     * @param $fieldNodes
850
     * @param ResolveInfo $info
851
     * @param $path
852
     * @param $result
853
     * @return array|null|Promise
854
     * @throws Error
855
     */
856 183
    public function completeValueWithLocatedError(
857
        Type $returnType,
858
        $fieldNodes,
859
        ResolveInfo $info,
860
        $path,
861
        $result
862
    )
863
    {
864
        try {
865 183
            $completed = $this->completeValue(
866 183
                $returnType,
867 183
                $fieldNodes,
868 183
                $info,
869 183
                $path,
870 183
                $result
871
            );
872 170
            $promise = $this->getPromise($completed);
873 170
            if ($promise) {
874
                return $promise->then(null, function ($error) use ($fieldNodes, $path) {
875 26
                    return $this->exeContext->promises->createRejected(Error::createLocatedError($error, $fieldNodes, $path));
876 38
                });
877
            }
878 150
            return $completed;
879 35
        } catch (\Exception $error) {
880 35
            throw Error::createLocatedError($error, $fieldNodes, $path);
881
        } catch (\Throwable $error) {
882
            throw Error::createLocatedError($error, $fieldNodes, $path);
883
        }
884
    }
885
886
    /**
887
     * Implements the instructions for completeValue as defined in the
888
     * "Field entries" section of the spec.
889
     *
890
     * If the field type is Non-Null, then this recursively completes the value
891
     * for the inner type. It throws a field error if that completion returns null,
892
     * as per the "Nullability" section of the spec.
893
     *
894
     * If the field type is a List, then this recursively completes the value
895
     * for the inner type on each item in the list.
896
     *
897
     * If the field type is a Scalar or Enum, ensures the completed value is a legal
898
     * value of the type by calling the `serialize` method of GraphQL type
899
     * definition.
900
     *
901
     * If the field is an abstract type, determine the runtime type of the value
902
     * and then complete based on that type
903
     *
904
     * Otherwise, the field type expects a sub-selection set, and will complete the
905
     * value by evaluating all sub-selections.
906
     *
907
     * @param Type $returnType
908
     * @param FieldNode[] $fieldNodes
909
     * @param ResolveInfo $info
910
     * @param array $path
911
     * @param $result
912
     * @return array|null|Promise
913
     * @throws Error
914
     * @throws \Throwable
915
     */
916 183
    private function completeValue(
917
        Type $returnType,
918
        $fieldNodes,
919
        ResolveInfo $info,
920
        $path,
921
        &$result
922
    )
923
    {
924 183
        $promise = $this->getPromise($result);
925
926
        // If result is a Promise, apply-lift over completeValue.
927 183
        if ($promise) {
928
            return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) {
929 29
                return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved);
930 32
            });
931
        }
932
933 181
        if ($result instanceof \Exception || $result instanceof \Throwable) {
934 16
            throw $result;
935
        }
936
937
        // If field type is NonNull, complete for inner type, and throw field error
938
        // if result is null.
939 174
        if ($returnType instanceof NonNull) {
940 44
            $completed = $this->completeValue(
941 44
                $returnType->getWrappedType(),
942 44
                $fieldNodes,
943 44
                $info,
944 44
                $path,
945 44
                $result
946
            );
947 44
            if ($completed === null) {
948 15
                throw new InvariantViolation(
949 15
                    'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
950
                );
951
            }
952 38
            return $completed;
953
        }
954
955
        // If result is null-like, return null.
956 174
        if (null === $result) {
957 47
            return null;
958
        }
959
960
        // If field type is List, complete each item in the list with the inner type
961 156
        if ($returnType instanceof ListOfType) {
962 56
            return $this->completeListValue($returnType, $fieldNodes, $info, $path, $result);
963
        }
964
965
        // Account for invalid schema definition when typeLoader returns different
966
        // instance than `resolveType` or $field->getType() or $arg->getType()
967 156
        if ($returnType !== $this->exeContext->schema->getType($returnType->name)) {
968 1
            $hint = "";
969 1
            if ($this->exeContext->schema->getConfig()->typeLoader) {
970 1
                $hint = "Make sure that type loader returns the same instance as defined in {$info->parentType}.{$info->fieldName}";
971
            }
972 1
            throw new InvariantViolation(
973 1
                "Schema must contain unique named types but contains multiple types named \"$returnType\". ".
974 1
                "$hint ".
975 1
                "(see http://webonyx.github.io/graphql-php/type-system/#type-registry)."
976
            );
977
        }
978
979
        // If field type is Scalar or Enum, serialize to a valid value, returning
980
        // null if serialization is not possible.
981 155
        if ($returnType instanceof LeafType) {
982 140
            return $this->completeLeafValue($returnType, $result);
983
        }
984
985 92
        if ($returnType instanceof AbstractType) {
986 30
            return $this->completeAbstractValue($returnType, $fieldNodes, $info, $path, $result);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->completeAb... $info, $path, $result) also could return the type stdClass which is incompatible with the documented return type null|GraphQL\Executor\Promise\Promise|array.
Loading history...
987
        }
988
989
        // Field type must be Object, Interface or Union and expect sub-selections.
990 63
        if ($returnType instanceof ObjectType) {
991 63
            return $this->completeObjectValue($returnType, $fieldNodes, $info, $path, $result);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->completeOb... $info, $path, $result) also could return the type stdClass which is incompatible with the documented return type null|GraphQL\Executor\Promise\Promise|array.
Loading history...
992
        }
993
994
        throw new \RuntimeException("Cannot complete value of unexpected type \"{$returnType}\".");
995
    }
996
997
    /**
998
     * If a resolve function is not given, then a default resolve behavior is used
999
     * which takes the property of the source object of the same name as the field
1000
     * and returns it as the result, or if it's a function, returns the result
1001
     * of calling that function while passing along args and context.
1002
     *
1003
     * @param $source
1004
     * @param $args
1005
     * @param $context
1006
     * @param ResolveInfo $info
1007
     *
1008
     * @return mixed|null
1009
     */
1010 115
    public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
1011
    {
1012 115
        $fieldName = $info->fieldName;
1013 115
        $property = null;
1014
1015 115
        if (is_array($source) || $source instanceof \ArrayAccess) {
1016 73
            if (isset($source[$fieldName])) {
1017 73
                $property = $source[$fieldName];
1018
            }
1019 42
        } else if (is_object($source)) {
1020 41
            if (isset($source->{$fieldName})) {
1021 41
                $property = $source->{$fieldName};
1022
            }
1023
        }
1024
1025 115
        return $property instanceof \Closure ? $property($source, $args, $context, $info) : $property;
1026
    }
1027
1028
    /**
1029
     * This method looks up the field on the given type definition.
1030
     * It has special casing for the two introspection fields, __schema
1031
     * and __typename. __typename is special because it can always be
1032
     * queried as a field, even in situations where no other fields
1033
     * are allowed, like on a Union. __schema could get automatically
1034
     * added to the query type, but that would require mutating type
1035
     * definitions, which would cause issues.
1036
     *
1037
     * @param Schema $schema
1038
     * @param ObjectType $parentType
1039
     * @param $fieldName
1040
     *
1041
     * @return FieldDefinition
1042
     */
1043 187
    private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName)
1044
    {
1045 187
        static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef;
1046
1047 187
        $schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef();
1048 187
        $typeMetaFieldDef = $typeMetaFieldDef ?: Introspection::typeMetaFieldDef();
1049 187
        $typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef();
1050
1051 187
        if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
1052 6
            return $schemaMetaFieldDef;
1053 187
        } else if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
1054 14
            return $typeMetaFieldDef;
1055 187
        } else if ($fieldName === $typeNameMetaFieldDef->name) {
1056 7
            return $typeNameMetaFieldDef;
1057
        }
1058
1059 187
        $tmp = $parentType->getFields();
1060 187
        return isset($tmp[$fieldName]) ? $tmp[$fieldName] : null;
1061
    }
1062
1063
    /**
1064
     * Complete a value of an abstract type by determining the runtime object type
1065
     * of that value, then complete the value for that type.
1066
     *
1067
     * @param AbstractType $returnType
1068
     * @param $fieldNodes
1069
     * @param ResolveInfo $info
1070
     * @param array $path
1071
     * @param $result
1072
     * @return mixed
1073
     * @throws Error
1074
     */
1075 30
    private function completeAbstractValue(AbstractType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
1076
    {
1077 30
        $exeContext = $this->exeContext;
1078 30
        $runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
1079
1080 30
        if (null === $runtimeType) {
1081 11
            $runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
0 ignored issues
show
Bug Best Practice introduced by
The method GraphQL\Executor\Executor::defaultTypeResolver() is not static, but was called statically. ( Ignorable by Annotation )

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

1081
            /** @scrutinizer ignore-call */ 
1082
            $runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
Loading history...
1082
        }
1083
1084 30
        $promise = $this->getPromise($runtimeType);
1085 30
        if ($promise) {
1086
            return $promise->then(function($resolvedRuntimeType) use ($returnType, $fieldNodes, $info, $path, &$result) {
1087 5
                return $this->completeObjectValue(
1088 5
                    $this->ensureValidRuntimeType(
1089 5
                        $resolvedRuntimeType,
1090 5
                        $returnType,
1091 5
                        $fieldNodes,
1092 5
                        $info,
1093 5
                        $result
1094
                    ),
1095 5
                    $fieldNodes,
1096 5
                    $info,
1097 5
                    $path,
1098 5
                    $result
1099
                );
1100 7
            });
1101
        }
1102
1103 23
        return $this->completeObjectValue(
1104 23
            $this->ensureValidRuntimeType(
1105 23
                $runtimeType,
0 ignored issues
show
Bug introduced by
It seems like $runtimeType can also be of type GraphQL\Executor\Promise\Promise; however, parameter $runtimeTypeOrName of GraphQL\Executor\Executo...nsureValidRuntimeType() does only seem to accept null|string|GraphQL\Type\Definition\ObjectType, maybe add an additional type check? ( Ignorable by Annotation )

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

1105
                /** @scrutinizer ignore-type */ $runtimeType,
Loading history...
1106 23
                $returnType,
1107 23
                $fieldNodes,
1108 23
                $info,
1109 23
                $result
1110
            ),
1111 22
            $fieldNodes,
1112 22
            $info,
1113 22
            $path,
1114 22
            $result
1115
        );
1116
    }
1117
1118
    /**
1119
     * @param string|ObjectType|null $runtimeTypeOrName
1120
     * @param AbstractType $returnType
1121
     * @param $fieldNodes
1122
     * @param ResolveInfo $info
1123
     * @param $result
1124
     * @return ObjectType
1125
     * @throws Error
1126
     */
1127 28
    private function ensureValidRuntimeType(
1128
        $runtimeTypeOrName,
1129
        AbstractType $returnType,
1130
        $fieldNodes,
0 ignored issues
show
Unused Code introduced by
The parameter $fieldNodes is not used and could be removed. ( Ignorable by Annotation )

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

1130
        /** @scrutinizer ignore-unused */ $fieldNodes,

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

Loading history...
1131
        ResolveInfo $info,
1132
        &$result
1133
    )
1134
    {
1135 28
        $runtimeType = is_string($runtimeTypeOrName) ?
1136 4
            $this->exeContext->schema->getType($runtimeTypeOrName) :
1137 28
            $runtimeTypeOrName;
1138
1139 28
        if (!$runtimeType instanceof ObjectType) {
1140
            throw new InvariantViolation(
1141
                "Abstract type {$returnType} must resolve to an Object type at " .
1142
                "runtime for field {$info->parentType}.{$info->fieldName} with " .
1143
                'value "' . Utils::printSafe($result) . '", received "'. Utils::printSafe($runtimeType) . '".' .
1144
                'Either the ' . $returnType . ' type should provide a "resolveType" ' .
0 ignored issues
show
Bug introduced by
Are you sure $returnType of type GraphQL\Type\Definition\AbstractType can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

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

1144
                'Either the ' . /** @scrutinizer ignore-type */ $returnType . ' type should provide a "resolveType" ' .
Loading history...
1145
                'function or each possible types should provide an "isTypeOf" function.'
1146
            );
1147
        }
1148
1149 28
        if (!$this->exeContext->schema->isPossibleType($returnType, $runtimeType)) {
1150 4
            throw new InvariantViolation(
1151 4
                "Runtime Object type \"$runtimeType\" is not a possible type for \"$returnType\"."
1152
            );
1153
        }
1154
1155 28
        if ($runtimeType !== $this->exeContext->schema->getType($runtimeType->name)) {
0 ignored issues
show
introduced by
The condition $runtimeType !== $this->...ype($runtimeType->name) is always true.
Loading history...
1156 1
            throw new InvariantViolation(
1157 1
                "Schema must contain unique named types but contains multiple types named \"$runtimeType\". ".
1158 1
                "Make sure that `resolveType` function of abstract type \"{$returnType}\" returns the same ".
1159 1
                "type instance as referenced anywhere else within the schema " .
1160 1
                "(see http://webonyx.github.io/graphql-php/type-system/#type-registry)."
1161
            );
1162
        }
1163
1164 27
        return $runtimeType;
1165
    }
1166
1167
    /**
1168
     * Complete a list value by completing each item in the list with the
1169
     * inner type
1170
     *
1171
     * @param ListOfType $returnType
1172
     * @param $fieldNodes
1173
     * @param ResolveInfo $info
1174
     * @param array $path
1175
     * @param $result
1176
     * @return array|Promise
1177
     * @throws \Exception
1178
     */
1179 56
    private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
1180
    {
1181 56
        $itemType = $returnType->getWrappedType();
1182 56
        Utils::invariant(
1183 56
            is_array($result) || $result instanceof \Traversable,
1184 56
            'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
1185
        );
1186 56
        $containsPromise = false;
1187
1188 56
        $i = 0;
1189 56
        $completedItems = [];
1190 56
        foreach ($result as $item) {
1191 56
            $fieldPath = $path;
1192 56
            $fieldPath[] = $i++;
1193 56
            $completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item);
1194 56
            if (!$containsPromise && $this->getPromise($completedItem)) {
1195 13
                $containsPromise = true;
1196
            }
1197 56
            $completedItems[] = $completedItem;
1198
        }
1199 56
        return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems;
1200
    }
1201
1202
    /**
1203
     * Complete a Scalar or Enum by serializing to a valid value, throwing if serialization is not possible.
1204
     *
1205
     * @param LeafType $returnType
1206
     * @param $result
1207
     * @return mixed
1208
     * @throws \Exception
1209
     */
1210 140
    private function completeLeafValue(LeafType $returnType, &$result)
1211
    {
1212
        try {
1213 140
            return $returnType->serialize($result);
1214 3
        } catch (\Exception $error) {
1215 3
            throw new InvariantViolation(
1216 3
                'Expected a value of type "'. Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result),
1217 3
                0,
1218 3
                $error
1219
            );
1220
        } catch (\Throwable $error) {
1221
            throw new InvariantViolation(
1222
                'Expected a value of type "'. Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result),
1223
                0,
1224
                $error
1225
            );
1226
        }
1227
    }
1228
1229
    /**
1230
     * Complete an Object value by executing all sub-selections.
1231
     *
1232
     * @param ObjectType $returnType
1233
     * @param $fieldNodes
1234
     * @param ResolveInfo $info
1235
     * @param array $path
1236
     * @param $result
1237
     * @return array|Promise|\stdClass
1238
     * @throws Error
1239
     */
1240 89
    private function completeObjectValue(ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
1241
    {
1242
        // If there is an isTypeOf predicate function, call it with the
1243
        // current result. If isTypeOf returns false, then raise an error rather
1244
        // than continuing execution.
1245 89
        $isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
1246
1247 89
        if (null !== $isTypeOf) {
1248 11
            $promise = $this->getPromise($isTypeOf);
1249 11
            if ($promise) {
1250
                return $promise->then(function($isTypeOfResult) use ($returnType, $fieldNodes, $info, $path, &$result) {
1251 2
                    if (!$isTypeOfResult) {
1252
                        throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
1253
                    }
1254
1255 2
                    return $this->collectAndExecuteSubfields(
1256 2
                        $returnType,
1257 2
                        $fieldNodes,
1258 2
                        $info,
1259 2
                        $path,
1260 2
                        $result
1261
                    );
1262 2
                });
1263
            }
1264 9
            if (!$isTypeOf) {
1265 1
                throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
1266
            }
1267
        }
1268
1269 87
        return $this->collectAndExecuteSubfields(
1270 87
            $returnType,
1271 87
            $fieldNodes,
1272 87
            $info,
1273 87
            $path,
1274 87
            $result
1275
        );
1276
    }
1277
1278
    /**
1279
     * @param ObjectType $returnType
1280
     * @param array $result
1281
     * @param FieldNode[] $fieldNodes
1282
     * @return Error
1283
     */
1284 1
    private function invalidReturnTypeError(
1285
        ObjectType $returnType,
1286
        $result,
1287
        $fieldNodes
1288
    )
1289
    {
1290 1
        return new Error(
1291 1
            'Expected value of type "' . $returnType->name . '" but got: ' . Utils::printSafe($result) . '.',
1292 1
            $fieldNodes
1293
        );
1294
    }
1295
1296
1297
    /**
1298
     * @param ObjectType $returnType
1299
     * @param FieldNode[] $fieldNodes
1300
     * @param ResolveInfo $info
1301
     * @param array $path
1302
     * @param array $result
1303
     * @return array|Promise|\stdClass
1304
     * @throws Error
1305
     */
1306 89
    private function collectAndExecuteSubfields(
1307
        ObjectType $returnType,
1308
        $fieldNodes,
1309
        ResolveInfo $info,
0 ignored issues
show
Unused Code introduced by
The parameter $info is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
1310
        $path,
1311
        &$result
1312
    )
1313
    {
1314
        // Collect sub-fields to execute to complete this value.
1315 89
        $subFieldNodes = new \ArrayObject();
1316 89
        $visitedFragmentNames = new \ArrayObject();
1317
1318 89
        foreach ($fieldNodes as $fieldNode) {
1319 89
            if (isset($fieldNode->selectionSet)) {
1320 89
                $subFieldNodes = $this->collectFields(
1321 89
                    $returnType,
1322 89
                    $fieldNode->selectionSet,
1323 89
                    $subFieldNodes,
1324 89
                    $visitedFragmentNames
1325
                );
1326
            }
1327
        }
1328
1329 89
        return $this->executeFields($returnType, $result, $path, $subFieldNodes);
1330
    }
1331
1332
    /**
1333
     * If a resolveType function is not given, then a default resolve behavior is
1334
     * used which attempts two strategies:
1335
     *
1336
     * First, See if the provided value has a `__typename` field defined, if so, use
1337
     * that value as name of the resolved type.
1338
     *
1339
     * Otherwise, test each possible type for the abstract type by calling
1340
     * isTypeOf for the object being coerced, returning the first type that matches.
1341
     *
1342
     * @param $value
1343
     * @param $context
1344
     * @param ResolveInfo $info
1345
     * @param AbstractType $abstractType
1346
     * @return ObjectType|Promise|null
1347
     */
1348 11
    private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractType $abstractType)
1349
    {
1350
        // First, look for `__typename`.
1351
        if (
1352 11
            $value !== null &&
1353 11
            is_array($value) &&
1354 11
            isset($value['__typename']) &&
1355 11
            is_string($value['__typename'])
1356
        ) {
1357 2
            return $value['__typename'];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value['__typename'] returns the type string which is incompatible with the documented return type null|GraphQL\Executor\Pr...e\Definition\ObjectType.
Loading history...
1358
        }
1359
1360 9
        if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) {
1361 1
            Warning::warnOnce(
1362 1
                "GraphQL Interface Type `{$abstractType->name}` returned `null` from it`s `resolveType` function ".
1363 1
                'for value: ' . Utils::printSafe($value) . '. Switching to slow resolution method using `isTypeOf` ' .
1364 1
                'of all possible implementations. It requires full schema scan and degrades query performance significantly. '.
1365 1
                ' Make sure your `resolveType` always returns valid implementation or throws.',
1366 1
                Warning::WARNING_FULL_SCHEMA_SCAN
1367
            );
1368
        }
1369
1370
        // Otherwise, test each possible type.
1371 9
        $possibleTypes = $info->schema->getPossibleTypes($abstractType);
1372 9
        $promisedIsTypeOfResults = [];
1373
1374 9
        foreach ($possibleTypes as $index => $type) {
1375 9
            $isTypeOfResult = $type->isTypeOf($value, $context, $info);
1376
1377 9
            if (null !== $isTypeOfResult) {
1378 9
                $promise = $this->getPromise($isTypeOfResult);
1379 9
                if ($promise) {
1380 3
                    $promisedIsTypeOfResults[$index] = $promise;
1381 6
                } else if ($isTypeOfResult) {
1382 9
                    return $type;
1383
                }
1384
            }
1385
        }
1386
1387 3
        if (!empty($promisedIsTypeOfResults)) {
1388 3
            return $this->exeContext->promises->all($promisedIsTypeOfResults)
1389 3
                ->then(function($isTypeOfResults) use ($possibleTypes) {
1390 2
                    foreach ($isTypeOfResults as $index => $result) {
1391 2
                        if ($result) {
1392 2
                            return $possibleTypes[$index];
1393
                        }
1394
                    }
1395
                    return null;
1396 3
                });
1397
        }
1398
1399
        return null;
1400
    }
1401
1402
    /**
1403
     * Only returns the value if it acts like a Promise, i.e. has a "then" function,
1404
     * otherwise returns null.
1405
     *
1406
     * @param mixed $value
1407
     * @return Promise|null
1408
     */
1409 187
    private function getPromise($value)
1410
    {
1411 187
        if (null === $value || $value instanceof Promise) {
1412 91
            return $value;
1413
        }
1414 185
        if ($this->exeContext->promises->isThenable($value)) {
1415 38
            $promise = $this->exeContext->promises->convertThenable($value);
1416 38
            if (!$promise instanceof Promise) {
1417
                throw new InvariantViolation(sprintf(
1418
                    '%s::convertThenable is expected to return instance of GraphQL\Executor\Promise\Promise, got: %s',
1419
                    get_class($this->exeContext->promises),
1420
                    Utils::printSafe($promise)
1421
                ));
1422
            }
1423 38
            return $promise;
1424
        }
1425 181
        return null;
1426
    }
1427
}
1428