Completed
Pull Request — master (#167)
by Sebastian
03:01
created

Processor::getExecutionContext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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