GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#1)
by Šimon
29:17 queued 25:57
created

Executor::executeFields()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 15
cts 15
cp 1
rs 8.439
c 0
b 0
f 0
cc 6
eloc 14
nc 8
nop 4
crap 6
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.
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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