Completed
Pull Request — master (#178)
by David
03:56
created

Processor   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 605
Duplicated Lines 5.95 %

Coupling/Cohesion

Components 1
Dependencies 31

Test Coverage

Coverage 95.45%

Importance

Changes 0
Metric Value
wmc 117
lcom 1
cbo 31
dl 36
loc 605
ccs 273
cts 286
cp 0.9545
rs 1.2912
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
F processPayload() 0 41 10
A unpackDeferredResults() 0 14 4
A resolveQuery() 0 18 4
C resolveField() 13 59 14
A prepareAstArguments() 0 10 3
C prepareArgumentValue() 0 64 19
C getVariableReferenceArgumentValue() 0 24 11
C collectResult() 23 55 10
A deferredResolve() 0 11 2
A resolveScalar() 0 12 1
C resolveList() 0 68 10
B resolveObject() 0 23 4
B resolveComposite() 0 39 5
A parseAndCreateRequest() 0 13 2
A doResolve() 0 8 2
B parseArgumentsValues() 0 25 5
A getAlias() 0 4 2
A createResolveInfo() 0 4 1
A getExecutionContext() 0 4 1
A getResponseData() 0 14 3
A getMaxComplexity() 0 4 1
A setMaxComplexity() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Processor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Processor, and based on these observations, apply Extract Interface, too.

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