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

Processor::unpackDeferredResults()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
ccs 10
cts 10
cp 1
cc 4
eloc 7
nc 4
nop 1
crap 4
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 70
        }
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 1
            }
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 2
            }
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 67
                };
96 67
            }
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 4
                  }
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 4
              } catch (\Exception $e) {
111
                  $this->executionContext->addError($e);
112 4
              } finally {
113 4
                  $this->data = static::unpackDeferredResults($this->data);
114 4
              }
115 4
            }
116
117 68
        } 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 4
        }
138
139 4
        if (is_array($result)) {
140 4
            foreach ($result as $key => $value) {
141 4
                $result[$key] = static::unpackDeferredResults($value);
142 4
            }
143 4
        }
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
            'type' => $type
155 67
        ]);
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 3
                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 61
                case TypeMap::KIND_SCALAR:
188 54
                    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 54
                    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 6 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 37
            }
235 66
        }
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 9
                    }
249 9
                } 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 1
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
265 1
                        }
266 5
                    }
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 5
                        } else {
271
                            $result[$key] = $item;
272
                        }
273 5
                    }
274 5
                } 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 36
            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 3
            ) {
312 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
313
            }
314 3
        } 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 39
            switch (true) {
342 39
                case $astField instanceof TypedFragmentReference:
343 3
                    $astName  = $astField->getTypeName();
344 3
                    $typeName = $type->getName();
345
346 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...
347 3
                        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
                                break;
352
                            }
353 3
                        }
354
355 3
                        continue 2;
356
                    }
357
358 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...
359
360 3
                    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 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...
368 1
                        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 1
                        }
375
376 1
                        continue 2;
377
                    }
378
379 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...
380
381 7
                    break;
382
383 39
                default:
384 39
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
385 39
            }
386 39
        }
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 4
            $deferredResult = new DeferredResult($resolvedValue, $callback);
397
398
            // Whenever we stumble upon a deferred resolver, add it to the queue
399
            // to be resolved later.
400 4
            $type = $field->getType()->getNamedType();
401 4
            if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) {
402
                array_push($this->deferredResultsLeaf, $deferredResult);
403
            } else {
404 4
                array_push($this->deferredResultsComplex, $deferredResult);
405
            }
406
407 4
            return $deferredResult;
408
        }
409
        // For simple values, invoke the callback immediately.
410 61
        return $callback($resolvedValue);
411
    }
412
413 55
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
414
    {
415 55
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
416
        return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) {
417 55
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
418
419
            /** @var AbstractScalarType $type */
420 54
            $type = $field->getType()->getNullableType();
421
422 54
            return $type->serialize($resolvedValue);
423 55
        });
424
    }
425
426 27
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
427
    {
428
        /** @var AstQuery $ast */
429 27
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
430
431
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
432 27
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
433
434 25
            if (null === $resolvedValue) {
435 7
                return null;
436
            }
437
438
            /** @var AbstractListType $type */
439 24
            $type     = $field->getType()->getNullableType();
440 24
            $itemType = $type->getNamedType();
441
442 24
            $fakeAst = clone $ast;
443 24
            if ($fakeAst instanceof AstQuery) {
444 23
                $fakeAst->setArguments([]);
445 23
            }
446
447 24
            $fakeField = new Field([
448 24
              'name' => $field->getName(),
449 24
              'type' => $itemType,
450 24
              'args' => $field->getArguments(),
451 24
            ]);
452
453 24
            $result = [];
454 24
            foreach ($resolvedValue as $resolvedValueItem) {
455
                try {
456
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
457 23
                        return $resolvedValueItem;
458 23
                    });
459
460 23
                    switch ($itemType->getNullableType()->getKind()) {
461 23
                        case TypeMap::KIND_ENUM:
462 23
                        case TypeMap::KIND_SCALAR:
463 5
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
464
465 4
                            break;
466
467
468 19
                        case TypeMap::KIND_OBJECT:
469 16
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
470
471 16
                            break;
472
473 4
                        case TypeMap::KIND_UNION:
474 4
                        case TypeMap::KIND_INTERFACE:
475 4
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
476
477 4
                            break;
478
479
                        default:
480
                            $value = null;
481 22
                    }
482 23
                } catch (\Exception $e) {
483 1
                    $this->executionContext->addError($e);
484
485 1
                    $value = null;
486
                }
487
488 23
                $result[] = $value;
489 24
            }
490
491 24
            return $result;
492 27
        });
493
    }
494
495 40
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
496
    {
497 40
        $resolvedValue = $parentValue;
498 40
        if (!$fromUnion) {
499 34
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
500 34
        }
501
502
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
503 40
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
504
505 40
            if (null === $resolvedValue) {
506 7
                return null;
507
            }
508
            /** @var AbstractObjectType $type */
509 39
            $type = $field->getType()->getNullableType();
510
511
            try {
512 39
                return $this->collectResult($field, $type, $ast, $resolvedValue);
513 4
            } catch (\Exception $e) {
514 4
                return null;
515
            }
516 40
        });
517
    }
518
519 8
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
520
    {
521
        /** @var AstQuery $ast */
522 8
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
523 8
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
524 8
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
525
526 8
            if (null === $resolvedValue) {
527
                return null;
528
            }
529
530
            /** @var AbstractUnionType $type */
531 8
            $type         = $field->getType()->getNullableType();
532 8
            $resolveInfo = new ResolveInfo(
533 8
                $field,
534 8
                $ast instanceof AstQuery ? $ast->getFields() : [],
535 8
                $this->executionContext
536 8
            );
537 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...
538
539 8
            if (!$resolvedType) {
540
                throw new ResolveException('Resolving function must return type');
541
            }
542
543 8
            if ($type instanceof AbstractInterfaceType) {
544 6
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
545 6
            } else {
546 2
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
547
            }
548
549 8
            $fakeField = new Field([
550 8
              'name' => $field->getName(),
551 8
              'type' => $resolvedType,
552 8
              'args' => $field->getArguments(),
553 8
            ]);
554
555 8
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
556 8
        });
557
    }
558
559 68
    protected function parseAndCreateRequest($payload, $variables = [])
560
    {
561 68
        if (empty($payload)) {
562 1
            throw new \InvalidArgumentException('Must provide an operation.');
563
        }
564
565 68
        $parser  = new Parser();
566 68
        $request = new Request($parser->parse($payload), $variables);
567
568 68
        (new RequestValidator())->validate($request);
569
570 67
        $this->executionContext->setRequest($request);
571 67
    }
572
573 61
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
574
    {
575
        /** @var AstQuery|AstField $ast */
576 61
        $arguments = $this->parseArgumentsValues($field, $ast);
577 61
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
578
579 61
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
580
    }
581
582 61
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
583
    {
584 61
        $values   = [];
585 61
        $defaults = [];
586
587 61
        foreach ($field->getArguments() as $argument) {
588
            /** @var $argument InputField */
589 44
            if ($argument->getConfig()->has('defaultValue')) {
590 8
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
591 8
            }
592 61
        }
593
594 61
        foreach ($ast->getArguments() as $astArgument) {
595 34
            $argument     = $field->getArgument($astArgument->getName());
596 34
            $argumentType = $argument->getType()->getNullableType();
597
598 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
599
600 34
            if (isset($defaults[$argument->getName()])) {
601 3
                unset($defaults[$argument->getName()]);
602 3
            }
603 61
        }
604
605 61
        return array_merge($values, $defaults);
606
    }
607
608 67
    private function getAlias(AstFieldInterface $ast)
609
    {
610 67
        return $ast->getAlias() ?: $ast->getName();
611
    }
612
613 61
    protected function createResolveInfo(FieldInterface $field, array $astFields)
614
    {
615 61
        return new ResolveInfo($field, $astFields, $this->executionContext);
616
    }
617
618
619
    /**
620
     * You can access ExecutionContext to check errors and inject dependencies
621
     *
622
     * @return ExecutionContext
623
     */
624 11
    public function getExecutionContext()
625
    {
626 11
        return $this->executionContext;
627
    }
628
629 67
    public function getResponseData()
630
    {
631 67
        $result = [];
632
633 67
        if (!empty($this->data)) {
634 66
            $result['data'] = $this->data;
635 66
        }
636
637 67
        if ($this->executionContext->hasErrors()) {
638 19
            $result['errors'] = $this->executionContext->getErrorsArray();
639 19
        }
640
641 67
        return $result;
642
    }
643
644
    /**
645
     * @return int
646
     */
647
    public function getMaxComplexity()
648
    {
649
        return $this->maxComplexity;
650
    }
651
652
    /**
653
     * @param int $maxComplexity
654
     */
655 1
    public function setMaxComplexity($maxComplexity)
656
    {
657 1
        $this->maxComplexity = $maxComplexity;
658 1
    }
659
660
}
661