Completed
Pull Request — master (#188)
by Sebastian
02:23
created

Processor::deferredResolve()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 8
cts 9
cp 0.8889
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 10
nc 3
nop 3
crap 4.0218
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 $deferredResults = [];
62
63 70
    public function __construct(AbstractSchema $schema)
64
    {
65 70
        if (empty($this->executionContext)) {
66 70
            $this->executionContext = new ExecutionContext($schema);
67 70
            $this->executionContext->setContainer(new Container());
68
        }
69
70 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...
71 70
    }
72
73 68
    public function processPayload($payload, $variables = [], $reducers = [])
74
    {
75 68
        $this->data = [];
76
77
        try {
78 68
            $this->parseAndCreateRequest($payload, $variables);
79
80 67
            if ($this->maxComplexity) {
81 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
82
            }
83
84 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...
85 2
                $reducer = new Reducer();
86 2
                $reducer->reduceQuery($this->executionContext, $reducers);
87
            }
88
89 67
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
90 67
                if ($operationResult = $this->resolveQuery($query)) {
91 67
                    $this->data = array_replace_recursive($this->data, $operationResult);
92
                };
93
            }
94
95
            // If the processor found any deferred results, resolve them now.
96 67
            if (!empty($this->data) && $this->deferredResults) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredResults 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...
97
              try {
98 4
                  while ($deferredResolver = array_shift($this->deferredResults)) {
99 4
                      $deferredResolver->resolve();
100
                  }
101 1
              } catch (\Exception $e) {
102 1
                  $this->executionContext->addError($e);
103 4
              } finally {
104 67
                  $this->data = static::unpackDeferredResults($this->data);
105
              }
106
            }
107
108 5
        } catch (\Exception $e) {
109 5
            $this->executionContext->addError($e);
110
        }
111
112 68
        return $this;
113
    }
114
115
    /**
116
     * Unpack results stored inside deferred resolvers.
117
     *
118
     * @param mixed $result
119
     *   The result ree.
120
     *
121
     * @return mixed
122
     *   The unpacked result.
123
     */
124 4
    public static function unpackDeferredResults($result)
125
    {
126 4
        while ($result instanceof DeferredResult) {
127 4
            $result = $result->result;
128
        }
129
130 4
        if (is_array($result)) {
131 4
            foreach ($result as $key => $value) {
132 4
                $result[$key] = static::unpackDeferredResults($value);
133
            }
134
        }
135
136 4
        return $result;
137
    }
138
139 67
    protected function resolveQuery(AstQuery $query)
140
    {
141 67
        $schema = $this->executionContext->getSchema();
142 67
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
143 67
        $field  = new Field([
144 67
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
145 67
            'type' => $type
146
        ]);
147
148 67
        if (self::TYPE_NAME_QUERY == $query->getName()) {
149 1
            return [$this->getAlias($query) => $type->getName()];
150
        }
151
152 67
        $this->resolveValidator->assetTypeHasField($type, $query);
153 67
        $value = $this->resolveField($field, $query);
154
155 67
        return [$this->getAlias($query) => $value];
156
    }
157
158 67
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
159
    {
160
        try {
161
            /** @var AbstractObjectType $type */
162 67
            $type        = $field->getType();
163 67
            $nonNullType = $type->getNullableType();
164
165 67
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
166 3
                return $nonNullType->getName();
167
            }
168
169 67
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
170
171 67
            $targetField = $nonNullType->getField($ast->getName());
172
173 67
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
174 66
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
175
176 61
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
177 61
                case TypeMap::KIND_ENUM:
178 60
                case TypeMap::KIND_SCALAR:
179 54
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
180 2
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation());
181
                    }
182
183 54
                    return $this->resolveScalar($targetField, $ast, $parentValue);
184
185 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...
186
                    /** @var $type AbstractObjectType */
187 30
                    if (!$ast instanceof AstQuery) {
188 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
189
                    }
190
191 30
                    return $this->resolveObject($targetField, $ast, $parentValue);
192
193 30
                case TypeMap::KIND_LIST:
194 27
                    return $this->resolveList($targetField, $ast, $parentValue);
195
196 6
                case TypeMap::KIND_UNION:
197 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...
198 6
                    if (!$ast instanceof AstQuery) {
199
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
200
                    }
201
202 6
                    return $this->resolveComposite($targetField, $ast, $parentValue);
203
204
                default:
205
                    throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind));
206
            }
207 17
        } catch (\Exception $e) {
208 17
            $this->executionContext->addError($e);
209
210 17
            if ($fromObject) {
211 4
                throw $e;
212
            }
213
214 15
            return null;
215
        }
216
    }
217
218 67
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
219
    {
220 67
        foreach ($query->getArguments() as $astArgument) {
221 38
            if ($field->hasArgument($astArgument->getName())) {
222 38
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();
223
224 38
                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
225
            }
226
        }
227 66
    }
228
229 38
    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
230
    {
231 38
        switch ($argumentType->getKind()) {
232 38
            case TypeMap::KIND_LIST:
233
                /** @var $argumentType AbstractListType */
234 12
                $result = [];
235 12
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
236 9
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
237 9
                    foreach ($list as $item) {
238 9
                        $result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request);
239
                    }
240
                } else {
241 3
                    if ($argumentValue instanceof VariableReference) {
242 3
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
243
                    }
244
                }
245
246 9
                return $result;
247
248 37
            case TypeMap::KIND_INPUT_OBJECT:
249
                /** @var $argumentType AbstractInputObjectType */
250 6
                $result = [];
251 6
                if ($argumentValue instanceof AstInputObject) {
252 5
                    foreach ($argumentType->getFields() as $field) {
253
                        /** @var $field Field */
254 5
                        if ($field->getConfig()->has('defaultValue')) {
255 5
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
256
                        }
257
                    }
258 5
                    foreach ($argumentValue->getValue() as $key => $item) {
259 5
                        if ($argumentType->hasField($key)) {
260 5
                            $result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request);
261
                        } else {
262 5
                            $result[$key] = $item;
263
                        }
264
                    }
265
                } else {
266 2
                    if ($argumentValue instanceof VariableReference) {
267
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
268
                    } else {
269 2
                        if (is_array($argumentValue)) {
270 1
                            return $argumentValue;
271
                        }
272
                    }
273
                }
274
275 6
                return $result;
276
277 36
            case TypeMap::KIND_SCALAR:
278 4
            case TypeMap::KIND_ENUM:
279
                /** @var $argumentValue AstLiteral|VariableReference */
280 36
                if ($argumentValue instanceof VariableReference) {
281 7
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
282
                } else {
283 31
                    if ($argumentValue instanceof AstLiteral) {
284 19
                        return $argumentValue->getValue();
285
                    } else {
286 13
                        return $argumentValue;
287
                    }
288
                }
289
        }
290
291
        throw new ResolveException('Argument type not supported');
292
    }
293
294 9
    private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request)
295
    {
296 9
        $variable = $variableReference->getVariable();
297 9
        if ($argumentType->getKind() === TypeMap::KIND_LIST) {
298
            if (
299 3
                (!$variable->isArray() && !is_array($variable->getValue())) ||
300 3
                ($variable->getTypeName() !== $argumentType->getNamedType()->getNullableType()->getName()) ||
301 3
                ($argumentType->getNamedType()->getKind() === TypeMap::KIND_NON_NULL && $variable->isArrayElementNullable())
302
            ) {
303 3
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
304
            }
305
        } else {
306 7
            if ($variable->getTypeName() !== $argumentType->getName()) {
307 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()), $variable->getLocation());
308
            }
309
        }
310
311 8
        $requestValue = $request->getVariable($variable->getName());
312 8
        if ((null === $requestValue && $variable->isNullable()) && !$request->hasVariable($variable->getName())) {
313
            throw new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()), $variable->getLocation());
314
        }
315
316 8
        return $requestValue;
317
    }
318
319
320
    /**
321
     * @param FieldInterface     $field
322
     * @param AbstractObjectType $type
323
     * @param AstFieldInterface  $ast
324
     * @param                    $resolvedValue
325
     * @return array
326
     */
327 39
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
328
    {
329 39
        $result = [];
330
331 39
        foreach ($ast->getFields() as $astField) {
332
            switch (true) {
333 39
                case $astField instanceof TypedFragmentReference:
334 3
                    $astName  = $astField->getTypeName();
335 3
                    $typeName = $type->getName();
336
337 3 View Code Duplication
                    if ($typeName !== $astName) {
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...
338 3
                        foreach ($type->getInterfaces() as $interface) {
339 1
                            if ($interface->getName() === $astName) {
340
                                $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...
341
342 1
                                break;
343
                            }
344
                        }
345
346 3
                        continue 2;
347
                    }
348
349 3
                    $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 3
                    break;
352
353 39
                case $astField instanceof FragmentReference:
354 7
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
355 7
                    $astFragmentModel = $astFragment->getModel();
356 7
                    $typeName         = $type->getName();
357
358 7 View Code Duplication
                    if ($typeName !== $astFragmentModel && $type->getInterfaces()) {
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...
359 1
                        foreach ($type->getInterfaces() as $interface) {
360 1
                            if ($interface->getName() === $astFragmentModel) {
361 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...
362
363 1
                                break;
364
                            }
365
                        }
366
367 1
                        continue 2;
368
                    }
369
370 7
                    $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 7
                    break;
373
374
                default:
375 39
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
376
            }
377
        }
378
379 39
        return $result;
380
    }
381
382
    /**
383
     * Apply post-process callbacks to all deferred resolvers.
384
     */
385 61
    protected function deferredResolve($resolvedValue, FieldInterface $field, callable $callback) {
386 61
        if ($resolvedValue instanceof DeferredResolverInterface) {
387 4
            $deferredResult = new DeferredResult($resolvedValue, $callback);
388
389
            // Whenever we stumble upon a deferred resolver, add it to the queue
390
            // to be resolved later.
391 4
            $type = $field->getType()->getNamedType();
392 4
            if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) {
393
                // Deferred scalar and enum fields should be resolved last to
394
                // pick up as many as possible for a single batch.
395
                array_push($this->deferredResults, $deferredResult);
396
            } else {
397
                // Prepend non-scalar / non-enum types to the queue to have them
398
                // resolved first as their sub-selections might yield additional
399
                // deferred results.
400 4
                array_unshift($this->deferredResults, $deferredResult);
401
            }
402
403 4
            return $deferredResult;
404
        }
405
        // For simple values, invoke the callback immediately.
406 61
        return $callback($resolvedValue);
407
    }
408
409 55
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
410
    {
411 55
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
412
        return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) {
413 55
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
414
415
            /** @var AbstractScalarType $type */
416 54
            $type = $field->getType()->getNullableType();
417
418 54
            return $type->serialize($resolvedValue);
419 55
        });
420
    }
421
422 27
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
423
    {
424
        /** @var AstQuery $ast */
425 27
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
426
427
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
428 27
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
429
430 25
            if (null === $resolvedValue) {
431 7
                return null;
432
            }
433
434
            /** @var AbstractListType $type */
435 24
            $type     = $field->getType()->getNullableType();
436 24
            $itemType = $type->getNamedType();
437
438 24
            $fakeAst = clone $ast;
439 24
            if ($fakeAst instanceof AstQuery) {
440 23
                $fakeAst->setArguments([]);
441
            }
442
443 24
            $fakeField = new Field([
444 24
              'name' => $field->getName(),
445 24
              'type' => $itemType,
446 24
              'args' => $field->getArguments(),
447
            ]);
448
449 24
            $result = [];
450 24
            foreach ($resolvedValue as $resolvedValueItem) {
451
                try {
452
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
453 23
                        return $resolvedValueItem;
454 23
                    });
455
456 23
                    switch ($itemType->getNullableType()->getKind()) {
457 23
                        case TypeMap::KIND_ENUM:
458 22
                        case TypeMap::KIND_SCALAR:
459 5
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
460
461 4
                            break;
462
463
464 19
                        case TypeMap::KIND_OBJECT:
465 16
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
466
467 16
                            break;
468
469 4
                        case TypeMap::KIND_UNION:
470 3
                        case TypeMap::KIND_INTERFACE:
471 4
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
472
473 4
                            break;
474
475
                        default:
476 22
                            $value = null;
477
                    }
478 1
                } catch (\Exception $e) {
479 1
                    $this->executionContext->addError($e);
480
481 1
                    $value = null;
482
                }
483
484 23
                $result[] = $value;
485
            }
486
487 24
            return $result;
488 27
        });
489
    }
490
491 40
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
492
    {
493 40
        $resolvedValue = $parentValue;
494 40
        if (!$fromUnion) {
495 34
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
496
        }
497
498
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
499 40
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
500
501 40
            if (null === $resolvedValue) {
502 7
                return null;
503
            }
504
            /** @var AbstractObjectType $type */
505 39
            $type = $field->getType()->getNullableType();
506
507
            try {
508 39
                return $this->collectResult($field, $type, $ast, $resolvedValue);
509 4
            } catch (\Exception $e) {
510 4
                return null;
511
            }
512 40
        });
513
    }
514
515 8
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
516
    {
517
        /** @var AstQuery $ast */
518 8
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
519 8
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
520 8
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
521
522 8
            if (null === $resolvedValue) {
523
                return null;
524
            }
525
526
            /** @var AbstractUnionType $type */
527 8
            $type         = $field->getType()->getNullableType();
528 8
            $resolveInfo = new ResolveInfo(
529 8
                $field,
530 8
                $ast instanceof AstQuery ? $ast->getFields() : [],
531 8
                $this->executionContext
532
            );
533 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...
534
535 8
            if (!$resolvedType) {
536
                throw new ResolveException('Resolving function must return type');
537
            }
538
539 8
            if ($type instanceof AbstractInterfaceType) {
540 6
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
541
            } else {
542 2
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
543
            }
544
545 8
            $fakeField = new Field([
546 8
              'name' => $field->getName(),
547 8
              'type' => $resolvedType,
548 8
              'args' => $field->getArguments(),
549
            ]);
550
551 8
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
552 8
        });
553
    }
554
555 68
    protected function parseAndCreateRequest($payload, $variables = [])
556
    {
557 68
        if (empty($payload)) {
558 1
            throw new \InvalidArgumentException('Must provide an operation.');
559
        }
560
561 68
        $parser  = new Parser();
562 68
        $request = new Request($parser->parse($payload), $variables);
563
564 68
        (new RequestValidator())->validate($request);
565
566 67
        $this->executionContext->setRequest($request);
567 67
    }
568
569 61
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
570
    {
571
        /** @var AstQuery|AstField $ast */
572 61
        $arguments = $this->parseArgumentsValues($field, $ast);
573 61
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
574
575 61
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
576
    }
577
578 61
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
579
    {
580 61
        $values   = [];
581 61
        $defaults = [];
582
583 61
        foreach ($field->getArguments() as $argument) {
584
            /** @var $argument InputField */
585 44
            if ($argument->getConfig()->has('defaultValue')) {
586 44
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
587
            }
588
        }
589
590 61
        foreach ($ast->getArguments() as $astArgument) {
591 34
            $argument     = $field->getArgument($astArgument->getName());
592 34
            $argumentType = $argument->getType()->getNullableType();
593
594 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
595
596 34
            if (isset($defaults[$argument->getName()])) {
597 34
                unset($defaults[$argument->getName()]);
598
            }
599
        }
600
601 61
        return array_merge($values, $defaults);
602
    }
603
604 67
    private function getAlias(AstFieldInterface $ast)
605
    {
606 67
        return $ast->getAlias() ?: $ast->getName();
607
    }
608
609 61
    protected function createResolveInfo(FieldInterface $field, array $astFields)
610
    {
611 61
        return new ResolveInfo($field, $astFields, $this->executionContext);
612
    }
613
614
615
    /**
616
     * You can access ExecutionContext to check errors and inject dependencies
617
     *
618
     * @return ExecutionContext
619
     */
620 11
    public function getExecutionContext()
621
    {
622 11
        return $this->executionContext;
623
    }
624
625 67
    public function getResponseData()
626
    {
627 67
        $result = [];
628
629 67
        if (!empty($this->data)) {
630 66
            $result['data'] = $this->data;
631
        }
632
633 67
        if ($this->executionContext->hasErrors()) {
634 20
            $result['errors'] = $this->executionContext->getErrorsArray();
635
        }
636
637 67
        return $result;
638
    }
639
640
    /**
641
     * @return int
642
     */
643
    public function getMaxComplexity()
644
    {
645
        return $this->maxComplexity;
646
    }
647
648
    /**
649
     * @param int $maxComplexity
650
     */
651 1
    public function setMaxComplexity($maxComplexity)
652
    {
653 1
        $this->maxComplexity = $maxComplexity;
654 1
    }
655
656
}
657