Completed
Push — master ( dcf687...6a9361 )
by Alexandr
05:22 queued 02:31
created

Processor::processPayload()   F

Complexity

Conditions 10
Paths 274

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 10.0862

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 19
cts 21
cp 0.9048
rs 3.1304
c 0
b 0
f 0
cc 10
eloc 23
nc 274
nop 3
crap 10.0862

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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