Completed
Pull Request — master (#181)
by
unknown
03:58
created

Processor::resolveList()   C

Complexity

Conditions 10
Paths 1

Size

Total Lines 68
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 10

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 68
ccs 37
cts 37
cp 1
rs 6.0995
cc 10
eloc 39
nc 1
nop 3
crap 10

How to fix   Long Method    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 70
    public function __construct(AbstractSchema $schema)
63
    {
64 70
        if (empty($this->executionContext)) {
65 70
            $this->executionContext = new ExecutionContext($schema);
66 70
            $this->executionContext->setContainer(new Container());
67
        }
68
69 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...
70 70
    }
71
72 68
    public function processPayload($payload, $variables = [], $reducers = [])
73
    {
74 68
        $this->data = [];
75
76
        try {
77 68
            $this->parseAndCreateRequest($payload, $variables);
78
79 67
            if ($this->maxComplexity) {
80 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
81
            }
82
83 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...
84 2
                $reducer = new Reducer();
85 2
                $reducer->reduceQuery($this->executionContext, $reducers);
86
            }
87
88 67
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
89 67
                if ($operationResult = $this->resolveQuery($query)) {
90 67
                    $this->data = array_replace_recursive($this->data, $operationResult);
91
                };
92
            }
93
94
            // If the processor found any deferred results, resolve them now.
95 67
            if (!empty($this->data) && $this->deferredResults) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredResults of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
96
              try {
97 4
                  while ($deferredResolver = array_shift($this->deferredResults)) {
98 4
                      $deferredResolver->resolve();
99
                  }
100
              } catch (\Exception $e) {
101
                  $this->executionContext->addError($e);
102 4
              } finally {
103 67
                  $this->data = static::unpackDeferredResults($this->data);
104
              }
105
            }
106
107 5
        } catch (\Exception $e) {
108 5
            $this->executionContext->addError($e);
109
        }
110
111 68
        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 4
    public static function unpackDeferredResults($result)
124
    {
125 4
        while ($result instanceof DeferredResult) {
126 4
            $result = $result->result;
127
        }
128
129 4
        if (is_array($result)) {
130 4
            foreach ($result as $key => $value) {
131 4
                $result[$key] = static::unpackDeferredResults($value);
132
            }
133
        }
134
135 4
        return $result;
136
    }
137
138 67
    protected function resolveQuery(AstQuery $query)
139
    {
140 67
        $schema = $this->executionContext->getSchema();
141 67
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
142 67
        $field  = new Field([
143 67
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
144 67
            'type' => $type
145
        ]);
146
147 67
        if (self::TYPE_NAME_QUERY == $query->getName()) {
148 1
            return [$this->getAlias($query) => $type->getName()];
149
        }
150
151 67
        $this->resolveValidator->assetTypeHasField($type, $query);
152 67
        $value = $this->resolveField($field, $query);
153
154 67
        return [$this->getAlias($query) => $value];
155
    }
156
157 67
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
158
    {
159
        try {
160
            /** @var AbstractObjectType $type */
161 67
            $type        = $field->getType();
162 67
            $nonNullType = $type->getNullableType();
163
164 67
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
165 3
                return $nonNullType->getName();
166
            }
167
168 67
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
169
170 67
            $targetField = $nonNullType->getField($ast->getName());
171
172 67
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
173 66
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
174
175 61
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
176 61
                case TypeMap::KIND_ENUM:
177 60
                case TypeMap::KIND_SCALAR:
178 54
                    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 54
                    return $this->resolveScalar($targetField, $ast, $parentValue);
183
184 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...
185
                    /** @var $type AbstractObjectType */
186 30
                    if (!$ast instanceof AstQuery) {
187 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
188
                    }
189
190 30
                    return $this->resolveObject($targetField, $ast, $parentValue);
191
192 30
                case TypeMap::KIND_LIST:
193 27
                    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 67
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
218
    {
219 67
        foreach ($query->getArguments() as $astArgument) {
220 38
            if ($field->hasArgument($astArgument->getName())) {
221 38
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();
222
223 38
                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
224
            }
225
        }
226 66
    }
227
228 38
    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
229
    {
230 38
        switch ($argumentType->getKind()) {
231 38
            case TypeMap::KIND_LIST:
232
                /** @var $argumentType AbstractListType */
233 12
                $result = [];
234 12
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
235 9
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
236 9
                    foreach ($list as $item) {
237 9
                        $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 9
                return $result;
246
247 37
            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 36
            case TypeMap::KIND_SCALAR:
277 4
            case TypeMap::KIND_ENUM:
278
                /** @var $argumentValue AstLiteral|VariableReference */
279 36
                if ($argumentValue instanceof VariableReference) {
280 7
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
281
                } else {
282 31
                    if ($argumentValue instanceof AstLiteral) {
283 19
                        return $argumentValue->getValue();
284
                    } else {
285 13
                        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 39
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
327
    {
328 39
        $result = [];
329
330 39
        $astFields = $ast->getFields();
331
332 39
        foreach ($astFields as $astField) {
333
            switch (true) {
334 39
                case $astField instanceof TypedFragmentReference:
335 3
                    $astName  = $astField->getTypeName();
336 3
                    $typeName = $type->getName();
337
338 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...
339 3
                        $interfaces = $type->getInterfaces();
340 3
                        foreach ($interfaces as $interface) {
341 1
                            if ($interface->getName() === $astName) {
342
                                $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...
343
344 1
                                break;
345
                            }
346
                        }
347
348 3
                        continue 2;
349
                    }
350
351 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...
352
353 3
                    break;
354
355 39
                case $astField instanceof FragmentReference:
356 7
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
357 7
                    $astFragmentModel = $astFragment->getModel();
358 7
                    $typeName         = $type->getName();
359 7
                    $interfaces       = $type->getInterfaces();
360
361 7 View Code Duplication
                    if ($typeName !== $astFragmentModel && $interfaces) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $interfaces of type Youshido\GraphQL\Type\In...AbstractInterfaceType[] 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...
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...
362 1
                        foreach ($interfaces as $interface) {
363 1
                            if ($interface->getName() === $astFragmentModel) {
364 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...
365
366 1
                                break;
367
                            }
368
                        }
369
370 1
                        continue 2;
371
                    }
372
373 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...
374
375 7
                    break;
376
377
                default:
378 39
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
379
            }
380
        }
381
382 39
        return $result;
383
    }
384
385
    /**
386
     * Apply post-process callbacks to all deferred resolvers.
387
     */
388 61
    protected function deferredResolve($resolvedValue, callable $callback) {
389 61
        if ($resolvedValue instanceof DeferredResolverInterface) {
390 4
            $deferredResult = new DeferredResult($resolvedValue, $callback);
391
            // Whenever we stumble upon a deferred resolver, append it to the
392
            // queue to be resolved later.
393 4
            $this->deferredResults[] = $deferredResult;
394 4
            return $deferredResult;
395
        }
396
        // For simple values, invoke the callback immediately.
397 61
        return $callback($resolvedValue);
398
    }
399
400 55
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
401
    {
402 55
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
403
        return $this->deferredResolve($resolvedValue, function($resolvedValue) use ($field, $ast, $parentValue) {
404 55
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
405
406
            /** @var AbstractScalarType $type */
407 54
            $type = $field->getType()->getNullableType();
408
409 54
            return $type->serialize($resolvedValue);
410 55
        });
411
    }
412
413 27
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
414
    {
415
        /** @var AstQuery $ast */
416 27
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
417
418
        return $this->deferredResolve($resolvedValue, function ($resolvedValue) use ($field, $ast, $parentValue) {
419 27
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
420
421 25
            if (null === $resolvedValue) {
422 7
                return null;
423
            }
424
425
            /** @var AbstractListType $type */
426 24
            $type     = $field->getType()->getNullableType();
427 24
            $itemType = $type->getNamedType();
428
429 24
            $fakeAst = clone $ast;
430 24
            if ($fakeAst instanceof AstQuery) {
431 23
                $fakeAst->setArguments([]);
432
            }
433
434 24
            $fakeField = new Field([
435 24
              'name' => $field->getName(),
436 24
              'type' => $itemType,
437 24
              'args' => $field->getArguments(),
438
            ]);
439
440 24
            $result = [];
441 24
            foreach ($resolvedValue as $resolvedValueItem) {
442
                try {
443
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
444 23
                        return $resolvedValueItem;
445 23
                    });
446
447 23
                    switch ($itemType->getNullableType()->getKind()) {
448 23
                        case TypeMap::KIND_ENUM:
449 22
                        case TypeMap::KIND_SCALAR:
450 5
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
451
452 4
                            break;
453
454
455 19
                        case TypeMap::KIND_OBJECT:
456 16
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
457
458 16
                            break;
459
460 4
                        case TypeMap::KIND_UNION:
461 3
                        case TypeMap::KIND_INTERFACE:
462 4
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
463
464 4
                            break;
465
466
                        default:
467 22
                            $value = null;
468
                    }
469 1
                } catch (\Exception $e) {
470 1
                    $this->executionContext->addError($e);
471
472 1
                    $value = null;
473
                }
474
475 23
                $result[] = $value;
476
            }
477
478 24
            return $result;
479 27
        });
480
    }
481
482 40
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
483
    {
484 40
        $resolvedValue = $parentValue;
485 40
        if (!$fromUnion) {
486 34
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
487
        }
488
489
        return $this->deferredResolve($resolvedValue, function ($resolvedValue) use ($field, $ast, $parentValue) {
490 40
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
491
492 40
            if (null === $resolvedValue) {
493 7
                return null;
494
            }
495
            /** @var AbstractObjectType $type */
496 39
            $type = $field->getType()->getNullableType();
497
498
            try {
499 39
                return $this->collectResult($field, $type, $ast, $resolvedValue);
500 4
            } catch (\Exception $e) {
501 4
                return null;
502
            }
503 40
        });
504
    }
505
506 8
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
507
    {
508
        /** @var AstQuery $ast */
509 8
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
510 8
        return $this->deferredResolve($resolvedValue, function ($resolvedValue) use ($field, $ast, $parentValue) {
511 8
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
512
513 8
            if (null === $resolvedValue) {
514
                return null;
515
            }
516
517
            /** @var AbstractUnionType $type */
518 8
            $type         = $field->getType()->getNullableType();
519 8
            $resolveInfo = new ResolveInfo(
520 8
                $field,
521 8
                $ast instanceof AstQuery ? $ast->getFields() : [],
522 8
                $this->executionContext
523
            );
524 8
            $resolvedType = $type->resolveType($resolvedValue, $resolveInfo);
525
526 8
            if (!$resolvedType) {
527
                throw new ResolveException('Resolving function must return type');
528
            }
529
530 8
            if ($type instanceof AbstractInterfaceType) {
531 6
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
532
            } else {
533 2
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
534
            }
535
536 8
            $fakeField = new Field([
537 8
              'name' => $field->getName(),
538 8
              'type' => $resolvedType,
539 8
              'args' => $field->getArguments(),
540
            ]);
541
542 8
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
543 8
        });
544
    }
545
546 68
    protected function parseAndCreateRequest($payload, $variables = [])
547
    {
548 68
        if (empty($payload)) {
549 1
            throw new \InvalidArgumentException('Must provide an operation.');
550
        }
551
552 68
        $parser  = new Parser();
553 68
        $request = new Request($parser->parse($payload), $variables);
554
555 68
        (new RequestValidator())->validate($request);
556
557 67
        $this->executionContext->setRequest($request);
558 67
    }
559
560 61
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
561
    {
562
        /** @var AstQuery|AstField $ast */
563 61
        $arguments = $this->parseArgumentsValues($field, $ast);
564 61
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
565
566 61
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
567
    }
568
569 61
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
570
    {
571 61
        $values   = [];
572 61
        $defaults = [];
573
574 61
        foreach ($field->getArguments() as $argument) {
575
            /** @var $argument InputField */
576 44
            if ($argument->getConfig()->has('defaultValue')) {
577 44
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
578
            }
579
        }
580
581 61
        foreach ($ast->getArguments() as $astArgument) {
582 34
            $argument     = $field->getArgument($astArgument->getName());
583 34
            $argumentType = $argument->getType()->getNullableType();
584
585 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
586
587 34
            if (isset($defaults[$argument->getName()])) {
588 34
                unset($defaults[$argument->getName()]);
589
            }
590
        }
591
592 61
        return array_merge($values, $defaults);
593
    }
594
595 67
    private function getAlias(AstFieldInterface $ast)
596
    {
597 67
        return $ast->getAlias() ?: $ast->getName();
598
    }
599
600 61
    protected function createResolveInfo(FieldInterface $field, array $astFields)
601
    {
602 61
        return new ResolveInfo($field, $astFields, $this->executionContext);
603
    }
604
605
606
    /**
607
     * You can access ExecutionContext to check errors and inject dependencies
608
     *
609
     * @return ExecutionContext
610
     */
611 11
    public function getExecutionContext()
612
    {
613 11
        return $this->executionContext;
614
    }
615
616 67
    public function getResponseData()
617
    {
618 67
        $result = [];
619
620 67
        if (!empty($this->data)) {
621 66
            $result['data'] = $this->data;
622
        }
623
624 67
        if ($this->executionContext->hasErrors()) {
625 19
            $result['errors'] = $this->executionContext->getErrorsArray();
626
        }
627
628 67
        return $result;
629
    }
630
631
    /**
632
     * @return int
633
     */
634
    public function getMaxComplexity()
635
    {
636
        return $this->maxComplexity;
637
    }
638
639
    /**
640
     * @param int $maxComplexity
641
     */
642 1
    public function setMaxComplexity($maxComplexity)
643
    {
644 1
        $this->maxComplexity = $maxComplexity;
645 1
    }
646
647
}
648