Completed
Pull Request — master (#200)
by Sebastian
03:22
created

Processor::deferredResolve()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 9
cts 10
cp 0.9
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 11
nc 3
nop 3
crap 4.016
1
<?php
2
/**
3
 * Date: 03.11.16
4
 *
5
 * @author Portey Vasil <[email protected]>
6
 */
7
8
namespace Youshido\GraphQL\Execution;
9
10
11
use Youshido\GraphQL\Exception\ResolveException;
12
use Youshido\GraphQL\Execution\Container\Container;
13
use Youshido\GraphQL\Execution\Context\ExecutionContext;
14
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
15
use Youshido\GraphQL\Field\Field;
16
use Youshido\GraphQL\Field\FieldInterface;
17
use Youshido\GraphQL\Field\InputField;
18
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList as AstInputList;
19
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject as AstInputObject;
20
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal as AstLiteral;
21
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
22
use Youshido\GraphQL\Parser\Ast\Field as AstField;
23
use Youshido\GraphQL\Parser\Ast\FragmentReference;
24
use Youshido\GraphQL\Parser\Ast\Interfaces\FieldInterface as AstFieldInterface;
25
use Youshido\GraphQL\Parser\Ast\Mutation as AstMutation;
26
use Youshido\GraphQL\Parser\Ast\Query as AstQuery;
27
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
28
use Youshido\GraphQL\Parser\Parser;
29
use Youshido\GraphQL\Schema\AbstractSchema;
30
use Youshido\GraphQL\Type\AbstractType;
31
use Youshido\GraphQL\Type\Enum\AbstractEnumType;
32
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
33
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
34
use Youshido\GraphQL\Type\ListType\AbstractListType;
35
use Youshido\GraphQL\Type\Object\AbstractObjectType;
36
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
37
use Youshido\GraphQL\Type\TypeMap;
38
use Youshido\GraphQL\Type\Union\AbstractUnionType;
39
use Youshido\GraphQL\Validator\RequestValidator\RequestValidator;
40
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
41
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
42
43
class Processor
44
{
45
46
    const TYPE_NAME_QUERY = '__typename';
47
48
    /** @var ExecutionContext */
49
    protected $executionContext;
50
51
    /** @var ResolveValidatorInterface */
52
    protected $resolveValidator;
53
54
    /** @var  array */
55
    protected $data;
56
57
    /** @var int */
58
    protected $maxComplexity;
59
60
    /** @var array DeferredResult[] */
61
    protected $deferredResultsLeaf = [];
62
63
    /** @var array DeferredResult[] */
64
    protected $deferredResultsComplex = [];
65
66 70
    public function __construct(AbstractSchema $schema)
67
    {
68 70
        if (empty($this->executionContext)) {
69 70
            $this->executionContext = new ExecutionContext($schema);
70 70
            $this->executionContext->setContainer(new Container());
71
        }
72
73 70
        $this->resolveValidator = new ResolveValidator($this->executionContext);
0 ignored issues
show
Unused Code introduced by
The call to ResolveValidator::__construct() has too many arguments starting with $this->executionContext.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
74 70
    }
75
76 68
    public function processPayload($payload, $variables = [], $reducers = [])
77
    {
78 68
        $this->data = [];
79
80
        try {
81 68
            $this->parseAndCreateRequest($payload, $variables);
82
83 67
            if ($this->maxComplexity) {
84 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
85
            }
86
87 67
            if ($reducers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $reducers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
88 2
                $reducer = new Reducer();
89 2
                $reducer->reduceQuery($this->executionContext, $reducers);
90
            }
91
92 67
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
93 67
                if ($operationResult = $this->resolveQuery($query)) {
94 67
                    $this->data = array_replace_recursive($this->data, $operationResult);
95
                };
96
            }
97
98
            // If the processor found any deferred results, resolve them now.
99 67
            if (!empty($this->data) && (!empty($this->deferredResultsLeaf) || !empty($this->deferredResultsComplex))) {
100
              try {
101 4
                  while ($deferredResolver = array_shift($this->deferredResultsComplex)) {
102 4
                      $deferredResolver->resolve();
103
                  }
104
105
                  // Deferred scalar and enum fields should be resolved last to
106
                  // pick up as many as possible for a single batch.
107 4
                  while ($deferredResolver = array_shift($this->deferredResultsLeaf)) {
108
                      $deferredResolver->resolve();
109
                  }
110
              } catch (\Exception $e) {
111
                  $this->executionContext->addError($e);
112 4
              } finally {
113 67
                  $this->data = static::unpackDeferredResults($this->data);
114
              }
115
            }
116
117 5
        } catch (\Exception $e) {
118 5
            $this->executionContext->addError($e);
119
        }
120
121 68
        return $this;
122
    }
123
124
    /**
125
     * Unpack results stored inside deferred resolvers.
126
     *
127
     * @param mixed $result
128
     *   The result ree.
129
     *
130
     * @return mixed
131
     *   The unpacked result.
132
     */
133 4
    public static function unpackDeferredResults($result)
134
    {
135 4
        while ($result instanceof DeferredResult) {
136 4
            $result = $result->result;
137
        }
138
139 4
        if (is_array($result)) {
140 4
            foreach ($result as $key => $value) {
141 4
                $result[$key] = static::unpackDeferredResults($value);
142
            }
143
        }
144
145 4
        return $result;
146
    }
147
148 67
    protected function resolveQuery(AstQuery $query)
149
    {
150 67
        $schema = $this->executionContext->getSchema();
151 67
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
152 67
        $field  = new Field([
153 67
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
154 67
            'type' => $type
155
        ]);
156
157 67
        if (self::TYPE_NAME_QUERY == $query->getName()) {
158 1
            return [$this->getAlias($query) => $type->getName()];
159
        }
160
161 67
        $this->resolveValidator->assetTypeHasField($type, $query);
162 67
        $value = $this->resolveField($field, $query);
163
164 67
        return [$this->getAlias($query) => $value];
165
    }
166
167 67
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
168
    {
169
        try {
170
            /** @var AbstractObjectType $type */
171 67
            $type        = $field->getType();
172 67
            $nonNullType = $type->getNullableType();
173
174 67
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
175 2
                return $nonNullType->getName();
176
            }
177
178 67
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
179
180 67
            $targetField = $nonNullType->getField($ast->getName());
181
182 67
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
183 66
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
184
185 61
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
186 61
                case TypeMap::KIND_ENUM:
187 60
                case TypeMap::KIND_SCALAR:
188 53
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
189 2
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation());
190
                    }
191
192 53
                    return $this->resolveScalar($targetField, $ast, $parentValue);
193
194 44 View Code Duplication
                case TypeMap::KIND_OBJECT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
195
                    /** @var $type AbstractObjectType */
196 30
                    if (!$ast instanceof AstQuery) {
197 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
198
                    }
199
200 30
                    return $this->resolveObject($targetField, $ast, $parentValue);
201
202 30
                case TypeMap::KIND_LIST:
203 27
                    return $this->resolveList($targetField, $ast, $parentValue);
204
205 6
                case TypeMap::KIND_UNION:
206 5 View Code Duplication
                case TypeMap::KIND_INTERFACE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207 6
                    if (!$ast instanceof AstQuery) {
208
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
209
                    }
210
211 6
                    return $this->resolveComposite($targetField, $ast, $parentValue);
212
213
                default:
214
                    throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind));
215
            }
216 17
        } catch (\Exception $e) {
217 17
            $this->executionContext->addError($e);
218
219 17
            if ($fromObject) {
220 4
                throw $e;
221
            }
222
223 15
            return null;
224
        }
225
    }
226
227 67
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
228
    {
229 67
        foreach ($query->getArguments() as $astArgument) {
230 38
            if ($field->hasArgument($astArgument->getName())) {
231 38
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();
232
233 38
                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
234
            }
235
        }
236 66
    }
237
238 38
    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
239
    {
240 38
        switch ($argumentType->getKind()) {
241 38
            case TypeMap::KIND_LIST:
242
                /** @var $argumentType AbstractListType */
243 12
                $result = [];
244 12
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
245 9
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
246 9
                    foreach ($list as $item) {
247 9
                        $result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request);
248
                    }
249
                } else {
250 3
                    if ($argumentValue instanceof VariableReference) {
251 3
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
252
                    }
253
                }
254
255 9
                return $result;
256
257 37
            case TypeMap::KIND_INPUT_OBJECT:
258
                /** @var $argumentType AbstractInputObjectType */
259 6
                $result = [];
260 6
                if ($argumentValue instanceof AstInputObject) {
261 5
                    foreach ($argumentType->getFields() as $field) {
262
                        /** @var $field Field */
263 5
                        if ($field->getConfig()->has('defaultValue')) {
264 5
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
265
                        }
266
                    }
267 5
                    foreach ($argumentValue->getValue() as $key => $item) {
268 5
                        if ($argumentType->hasField($key)) {
269 5
                            $result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request);
270
                        } else {
271 5
                            $result[$key] = $item;
272
                        }
273
                    }
274
                } else {
275 2
                    if ($argumentValue instanceof VariableReference) {
276
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
277
                    } else {
278 2
                        if (is_array($argumentValue)) {
279 1
                            return $argumentValue;
280
                        }
281
                    }
282
                }
283
284 6
                return $result;
285
286 36
            case TypeMap::KIND_SCALAR:
287 4
            case TypeMap::KIND_ENUM:
288
                /** @var $argumentValue AstLiteral|VariableReference */
289 36
                if ($argumentValue instanceof VariableReference) {
290 7
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
291
                } else {
292 31
                    if ($argumentValue instanceof AstLiteral) {
293 19
                        return $argumentValue->getValue();
294
                    } else {
295 13
                        return $argumentValue;
296
                    }
297
                }
298
        }
299
300
        throw new ResolveException('Argument type not supported');
301
    }
302
303 9
    private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request)
304
    {
305 9
        $variable = $variableReference->getVariable();
306 9
        if ($argumentType->getKind() === TypeMap::KIND_LIST) {
307
            if (
308 3
                (!$variable->isArray() && !is_array($variable->getValue())) ||
309 3
                ($variable->getTypeName() !== $argumentType->getNamedType()->getNullableType()->getName()) ||
310 3
                ($argumentType->getNamedType()->getKind() === TypeMap::KIND_NON_NULL && $variable->isArrayElementNullable())
311
            ) {
312 3
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
313
            }
314
        } else {
315 7
            if ($variable->getTypeName() !== $argumentType->getName()) {
316 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()), $variable->getLocation());
317
            }
318
        }
319
320 8
        $requestValue = $request->getVariable($variable->getName());
321 8
        if ((null === $requestValue && $variable->isNullable()) && !$request->hasVariable($variable->getName())) {
322
            throw new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()), $variable->getLocation());
323
        }
324
325 8
        return $requestValue;
326
    }
327
328
329
    /**
330
     * @param FieldInterface     $field
331
     * @param AbstractObjectType $type
332
     * @param AstFieldInterface  $ast
333
     * @param                    $resolvedValue
334
     * @return array
335
     */
336 39
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
337
    {
338 39
        $result = [];
339
340 39
        foreach ($ast->getFields() as $astField) {
341
            switch (true) {
342 39
                case $astField instanceof TypedFragmentReference:
343 2
                    $astName  = $astField->getTypeName();
344 2
                    $typeName = $type->getName();
345
346 2
                    if ($typeName !== $astName) {
347 2
                        foreach ($type->getInterfaces() as $interface) {
348 1
                            if ($interface->getName() === $astName) {
349
                                $result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astField is of type object<Youshido\GraphQL\...TypedFragmentReference>, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
350
351 1
                                break;
352
                            }
353
                        }
354
355 2
                        continue 2;
356
                    }
357
358 2
                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astField is of type object<Youshido\GraphQL\...TypedFragmentReference>, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
359
360 2
                    break;
361
362 39
                case $astField instanceof FragmentReference:
363 7
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
364 7
                    $astFragmentModel = $astFragment->getModel();
365 7
                    $typeName         = $type->getName();
366
367 7
                    if ($typeName !== $astFragmentModel) {
368 2
                        foreach ($type->getInterfaces() as $interface) {
369 1
                            if ($interface->getName() === $astFragmentModel) {
370 1
                                $result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astFragment is of type object<Youshido\GraphQL\Parser\Ast\Fragment>|null, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
371
372 1
                                break;
373
                            }
374
                        }
375
376 2
                        continue 2;
377
                    }
378
379 6
                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astFragment is of type object<Youshido\GraphQL\Parser\Ast\Fragment>|null, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
380
381 6
                    break;
382
383
                default:
384 38
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
385
            }
386
        }
387
388 39
        return $result;
389
    }
390
391
    /**
392
     * Apply post-process callbacks to all deferred resolvers.
393
     */
394 61
    protected function deferredResolve($resolvedValue, FieldInterface $field, callable $callback) {
395 61
        if ($resolvedValue instanceof DeferredResolverInterface) {
396
            $deferredResult = new DeferredResult($resolvedValue, function ($resolvedValue) use ($field, $callback) {
397
                // Allow nested deferred resolvers.
398 4
                return $this->deferredResolve($resolvedValue, $field, $callback);
399 4
            });
400
401
            // Whenever we stumble upon a deferred resolver, add it to the queue
402
            // to be resolved later.
403 4
            $type = $field->getType()->getNamedType();
404 4
            if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) {
405
                array_push($this->deferredResultsLeaf, $deferredResult);
406
            } else {
407 4
                array_push($this->deferredResultsComplex, $deferredResult);
408
            }
409
410 4
            return $deferredResult;
411
        }
412
        // For simple values, invoke the callback immediately.
413 61
        return $callback($resolvedValue);
414
    }
415
416 54
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
417
    {
418 54
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
419
        return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) {
420 54
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
421
422
            /** @var AbstractScalarType $type */
423 53
            $type = $field->getType()->getNullableType();
424
425 53
            return $type->serialize($resolvedValue);
426 54
        });
427
    }
428
429 27
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
430
    {
431
        /** @var AstQuery $ast */
432 27
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
433
434
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
435 27
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
436
437 25
            if (null === $resolvedValue) {
438 7
                return null;
439
            }
440
441
            /** @var AbstractListType $type */
442 24
            $type     = $field->getType()->getNullableType();
443 24
            $itemType = $type->getNamedType();
444
445 24
            $fakeAst = clone $ast;
446 24
            if ($fakeAst instanceof AstQuery) {
447 23
                $fakeAst->setArguments([]);
448
            }
449
450 24
            $fakeField = new Field([
451 24
              'name' => $field->getName(),
452 24
              'type' => $itemType,
453 24
              'args' => $field->getArguments(),
454
            ]);
455
456 24
            $result = [];
457 24
            foreach ($resolvedValue as $resolvedValueItem) {
458
                try {
459
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
460 23
                        return $resolvedValueItem;
461 23
                    });
462
463 23
                    switch ($itemType->getNullableType()->getKind()) {
464 23
                        case TypeMap::KIND_ENUM:
465 22
                        case TypeMap::KIND_SCALAR:
466 5
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
467
468 4
                            break;
469
470
471 19
                        case TypeMap::KIND_OBJECT:
472 16
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
473
474 16
                            break;
475
476 4
                        case TypeMap::KIND_UNION:
477 3
                        case TypeMap::KIND_INTERFACE:
478 4
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
479
480 4
                            break;
481
482
                        default:
483 22
                            $value = null;
484
                    }
485 1
                } catch (\Exception $e) {
486 1
                    $this->executionContext->addError($e);
487
488 1
                    $value = null;
489
                }
490
491 23
                $result[] = $value;
492
            }
493
494 24
            return $result;
495 27
        });
496
    }
497
498 40
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
499
    {
500 40
        $resolvedValue = $parentValue;
501 40
        if (!$fromUnion) {
502 34
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
503
        }
504
505
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
506 40
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
507
508 40
            if (null === $resolvedValue) {
509 7
                return null;
510
            }
511
            /** @var AbstractObjectType $type */
512 39
            $type = $field->getType()->getNullableType();
513
514
            try {
515 39
                return $this->collectResult($field, $type, $ast, $resolvedValue);
516 4
            } catch (\Exception $e) {
517 4
                return null;
518
            }
519 40
        });
520
    }
521
522 8
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
523
    {
524
        /** @var AstQuery $ast */
525 8
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
526 8
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
527 8
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
528
529 8
            if (null === $resolvedValue) {
530
                return null;
531
            }
532
533
            /** @var AbstractUnionType $type */
534 8
            $type         = $field->getType()->getNullableType();
535 8
            $resolveInfo = new ResolveInfo(
536 8
                $field,
537 8
                $ast instanceof AstQuery ? $ast->getFields() : [],
538 8
                $this->executionContext
539
            );
540 8
            $resolvedType = $type->resolveType($resolvedValue, $resolveInfo);
0 ignored issues
show
Unused Code introduced by
The call to AbstractUnionType::resolveType() has too many arguments starting with $resolveInfo.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
541
542 8
            if (!$resolvedType) {
543
                throw new ResolveException('Resolving function must return type');
544
            }
545
546 8
            if ($type instanceof AbstractInterfaceType) {
547 6
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
548
            } else {
549 2
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
550
            }
551
552 8
            $fakeField = new Field([
553 8
              'name' => $field->getName(),
554 8
              'type' => $resolvedType,
555 8
              'args' => $field->getArguments(),
556
            ]);
557
558 8
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
559 8
        });
560
    }
561
562 68
    protected function parseAndCreateRequest($payload, $variables = [])
563
    {
564 68
        if (empty($payload)) {
565 1
            throw new \InvalidArgumentException('Must provide an operation.');
566
        }
567
568 68
        $parser  = new Parser();
569 68
        $request = new Request($parser->parse($payload), $variables);
570
571 68
        (new RequestValidator())->validate($request);
572
573 67
        $this->executionContext->setRequest($request);
574 67
    }
575
576 61
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
577
    {
578
        /** @var AstQuery|AstField $ast */
579 61
        $arguments = $this->parseArgumentsValues($field, $ast);
580 61
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
581
582 61
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
583
    }
584
585 61
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
586
    {
587 61
        $values   = [];
588 61
        $defaults = [];
589
590 61
        foreach ($field->getArguments() as $argument) {
591
            /** @var $argument InputField */
592 44
            if ($argument->getConfig()->has('defaultValue')) {
593 44
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
594
            }
595
        }
596
597 61
        foreach ($ast->getArguments() as $astArgument) {
598 34
            $argument     = $field->getArgument($astArgument->getName());
599 34
            $argumentType = $argument->getType()->getNullableType();
600
601 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
602
603 34
            if (isset($defaults[$argument->getName()])) {
604 34
                unset($defaults[$argument->getName()]);
605
            }
606
        }
607
608 61
        return array_merge($values, $defaults);
609
    }
610
611 67
    private function getAlias(AstFieldInterface $ast)
612
    {
613 67
        return $ast->getAlias() ?: $ast->getName();
614
    }
615
616 61
    protected function createResolveInfo(FieldInterface $field, array $astFields)
617
    {
618 61
        return new ResolveInfo($field, $astFields, $this->executionContext);
619
    }
620
621
622
    /**
623
     * You can access ExecutionContext to check errors and inject dependencies
624
     *
625
     * @return ExecutionContext
626
     */
627 11
    public function getExecutionContext()
628
    {
629 11
        return $this->executionContext;
630
    }
631
632 67
    public function getResponseData()
633
    {
634 67
        $result = [];
635
636 67
        if (!empty($this->data)) {
637 66
            $result['data'] = $this->data;
638
        }
639
640 67
        if ($this->executionContext->hasErrors()) {
641 19
            $result['errors'] = $this->executionContext->getErrorsArray();
642
        }
643
644 67
        return $result;
645
    }
646
647
    /**
648
     * @return int
649
     */
650
    public function getMaxComplexity()
651
    {
652
        return $this->maxComplexity;
653
    }
654
655
    /**
656
     * @param int $maxComplexity
657
     */
658 1
    public function setMaxComplexity($maxComplexity)
659
    {
660 1
        $this->maxComplexity = $maxComplexity;
661 1
    }
662
663
}
664