Completed
Push — master ( 67ba27...61c2d2 )
by Alexandr
03:23
created

Processor   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 603
Duplicated Lines 5.97 %

Coupling/Cohesion

Components 1
Dependencies 31

Test Coverage

Coverage 94.55%

Importance

Changes 0
Metric Value
wmc 117
lcom 1
cbo 31
dl 36
loc 603
ccs 312
cts 330
cp 0.9455
rs 1.2965
c 0
b 0
f 0

23 Methods

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

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 67
    public function __construct(AbstractSchema $schema)
63
    {
64 67
        if (empty($this->executionContext)) {
65 67
            $this->executionContext = new ExecutionContext($schema);
66 67
            $this->executionContext->setContainer(new Container());
67 67
        }
68
69 67
        $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 67
    }
71
72 65
    public function processPayload($payload, $variables = [], $reducers = [])
73
    {
74 65
        $this->data = [];
75
76
        try {
77 65
            $this->parseAndCreateRequest($payload, $variables);
78
79 64
            if ($this->maxComplexity) {
80 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
81 1
            }
82
83 64
            if ($reducers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $reducers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
84 2
                $reducer = new Reducer();
85 2
                $reducer->reduceQuery($this->executionContext, $reducers);
86 2
            }
87
88 64
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
89 64
                if ($operationResult = $this->resolveQuery($query)) {
90 64
                    $this->data = array_merge($this->data, $operationResult);
91 64
                };
92 64
            }
93
94
            // If the processor found any deferred results, resolve them now.
95 64
            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 4
                  }
100 4
              } catch (\Exception $e) {
101
                  $this->executionContext->addError($e);
102 4
              } finally {
103 4
                  $this->data = static::unpackDeferredResults($this->data);
104
              }
105 4
            }
106
107 65
        } catch (\Exception $e) {
108 5
            $this->executionContext->addError($e);
109
        }
110
111 65
        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 4
        }
128
129 4
        if (is_array($result)) {
130 4
            foreach ($result as $key => $value) {
131 4
                $result[$key] = static::unpackDeferredResults($value);
132 4
            }
133 4
        }
134
135 4
        return $result;
136
    }
137
138 64
    protected function resolveQuery(AstQuery $query)
139
    {
140 64
        $schema = $this->executionContext->getSchema();
141 64
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
142 64
        $field  = new Field([
143 64
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
144
            'type' => $type
145 64
        ]);
146
147 64
        if (self::TYPE_NAME_QUERY == $query->getName()) {
148 1
            return [$this->getAlias($query) => $type->getName()];
149
        }
150
151 64
        $this->resolveValidator->assetTypeHasField($type, $query);
152 64
        $value = $this->resolveField($field, $query);
153
154 64
        return [$this->getAlias($query) => $value];
155
    }
156
157 64
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
158
    {
159
        try {
160
            /** @var AbstractObjectType $type */
161 64
            $type        = $field->getType();
162 64
            $nonNullType = $type->getNullableType();
163
164 64
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
165 2
                return $nonNullType->getName();
166
            }
167
168 64
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
169
170 64
            $targetField = $nonNullType->getField($ast->getName());
171
172 64
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
173 63
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
174
175 58
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
176 58
                case TypeMap::KIND_ENUM:
177 58
                case TypeMap::KIND_SCALAR:
178 51
                    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 51
                    return $this->resolveScalar($targetField, $ast, $parentValue);
183
184 41 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 28
                    if (!$ast instanceof AstQuery) {
187 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
188
                    }
189
190 28
                    return $this->resolveObject($targetField, $ast, $parentValue);
191
192 27
                case TypeMap::KIND_LIST:
193 24
                    return $this->resolveList($targetField, $ast, $parentValue);
194
195 6
                case TypeMap::KIND_UNION:
196 6 View Code Duplication
                case TypeMap::KIND_INTERFACE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 64
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
218
    {
219 64
        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 37
            }
225 63
        }
226 63
    }
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 9
                    }
239 9
                } 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 1
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
255 1
                        }
256 5
                    }
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 5
                        } else {
261
                            $result[$key] = $item;
262
                        }
263 5
                    }
264 5
                } 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 36
            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 3
            ) {
302 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
303
            }
304 3
        } 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 36
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
327
    {
328 36
        $result = [];
329
330 36
        foreach ($ast->getFields() as $astField) {
331 36
            switch (true) {
332 36
                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
                                break;
342
                            }
343 2
                        }
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 36
                case $astField instanceof FragmentReference:
353 5
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
354 5
                    $astFragmentModel = $astFragment->getModel();
355 5
                    $typeName         = $type->getName();
356
357 5 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 1
                        }
366
367 1
                        continue;
368
                    }
369
370 5
                    $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 5
                    break;
373
374 36
                default:
375 36
                    $result[$this->getAlias($astField)] = $this->resolveField($field, $astField, $resolvedValue, true);
376 36
            }
377 36
        }
378
379 36
        return $result;
380
    }
381
382
    /**
383
     * Apply post-process callbacks to all deferred resolvers.
384
     */
385 58
    protected function deferredResolve($resolvedValue, callable $callback) {
386 58
        if ($resolvedValue instanceof DeferredResolverInterface) {
387 4
            $deferredResult = new DeferredResult($resolvedValue, $callback);
388
            // Whenever we stumble upon a deferred resolver, append it to the
389
            // queue to be resolved later.
390 4
            $this->deferredResults[] = $deferredResult;
391 4
            return $deferredResult;
392
        }
393
        // For simple values, invoke the callback immediately.
394 58
        return $callback($resolvedValue);
395
    }
396
397 52
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
398
    {
399 52
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
400
        return $this->deferredResolve($resolvedValue, function($resolvedValue) use ($field, $ast, $parentValue) {
401 52
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
402
403
            /** @var AbstractScalarType $type */
404 51
            $type = $field->getType()->getNullableType();
405
406 51
            return $type->serialize($resolvedValue);
407 52
        });
408
    }
409
410 24
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
411
    {
412
        /** @var AstQuery $ast */
413 24
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
414
415
        return $this->deferredResolve($resolvedValue, function ($resolvedValue) use ($field, $ast, $parentValue) {
416 24
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
417
418 22
            if (null === $resolvedValue) {
419 6
                return null;
420
            }
421
422
            /** @var AbstractListType $type */
423 21
            $type     = $field->getType()->getNullableType();
424 21
            $itemType = $type->getNamedType();
425
426 21
            $fakeAst = clone $ast;
427 21
            if ($fakeAst instanceof AstQuery) {
428 20
                $fakeAst->setArguments([]);
429 20
            }
430
431 21
            $fakeField = new Field([
432 21
              'name' => $field->getName(),
433 21
              'type' => $itemType,
434 21
              'args' => $field->getArguments(),
435 21
            ]);
436
437 21
            $result = [];
438 21
            foreach ($resolvedValue as $resolvedValueItem) {
439
                try {
440
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
441 20
                        return $resolvedValueItem;
442 20
                    });
443
444 20
                    switch ($itemType->getNullableType()->getKind()) {
445 20
                        case TypeMap::KIND_ENUM:
446 20
                        case TypeMap::KIND_SCALAR:
447 5
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
448
449 4
                            break;
450
451
452 16
                        case TypeMap::KIND_OBJECT:
453 14
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
454
455 14
                            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
                            $value = null;
465 19
                    }
466 20
                } catch (\Exception $e) {
467 1
                    $this->executionContext->addError($e);
468
469 1
                    $value = null;
470
                }
471
472 20
                $result[] = $value;
473 21
            }
474
475 21
            return $result;
476 24
        });
477
    }
478
479 37
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
480
    {
481 37
        $resolvedValue = $parentValue;
482 37
        if (!$fromUnion) {
483 32
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
484 32
        }
485
486
        return $this->deferredResolve($resolvedValue, function ($resolvedValue) use ($field, $ast, $parentValue) {
487 37
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
488
489 37
            if (null === $resolvedValue) {
490 6
                return null;
491
            }
492
            /** @var AbstractObjectType $type */
493 36
            $type = $field->getType()->getNullableType();
494
495
            try {
496 36
                return $this->collectResult($field, $type, $ast, $resolvedValue);
497 4
            } catch (\Exception $e) {
498 4
                return null;
499
            }
500 37
        });
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 7
            );
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 6
            } 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 7
            ]);
538
539 7
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
540 7
        });
541
    }
542
543 65
    protected function parseAndCreateRequest($payload, $variables = [])
544
    {
545 65
        if (empty($payload)) {
546 1
            throw new \InvalidArgumentException('Must provide an operation.');
547
        }
548
549 65
        $parser  = new Parser();
550 65
        $request = new Request($parser->parse($payload), $variables);
551
552 65
        (new RequestValidator())->validate($request);
553
554 64
        $this->executionContext->setRequest($request);
555 64
    }
556
557 58
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
558
    {
559
        /** @var AstQuery|AstField $ast */
560 58
        $arguments = $this->parseArgumentsValues($field, $ast);
561 58
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
562
563 58
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
564
    }
565
566 58
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
567
    {
568 58
        $values   = [];
569 58
        $defaults = [];
570
571 58
        foreach ($field->getArguments() as $argument) {
572
            /** @var $argument InputField */
573 43
            if ($argument->getConfig()->has('defaultValue')) {
574 7
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
575 7
            }
576 58
        }
577
578 58
        foreach ($ast->getArguments() as $astArgument) {
579 34
            $argument     = $field->getArgument($astArgument->getName());
580 34
            $argumentType = $argument->getType()->getNullableType();
581
582 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
583
584 34
            if (isset($defaults[$argument->getName()])) {
585 3
                unset($defaults[$argument->getName()]);
586 3
            }
587 58
        }
588
589 58
        return array_merge($values, $defaults);
590
    }
591
592 64
    private function getAlias(AstFieldInterface $ast)
593
    {
594 64
        return $ast->getAlias() ?: $ast->getName();
595
    }
596
597 58
    protected function createResolveInfo(FieldInterface $field, array $astFields)
598
    {
599 58
        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 63
            $result['data'] = $this->data;
619 63
        }
620
621 64
        if ($this->executionContext->hasErrors()) {
622 19
            $result['errors'] = $this->executionContext->getErrorsArray();
623 19
        }
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