Completed
Pull Request — master (#146)
by
unknown
04:36
created

Processor   F

Complexity

Total Complexity 107

Size/Duplication

Total Lines 545
Duplicated Lines 6.61 %

Coupling/Cohesion

Components 1
Dependencies 34

Test Coverage

Coverage 96.17%

Importance

Changes 0
Metric Value
wmc 107
lcom 1
cbo 34
dl 36
loc 545
ccs 251
cts 261
cp 0.9617
rs 1.3043
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveQuery() 0 18 4
A resolveScalar() 0 11 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
A __construct() 0 16 3
B processPayload() 0 27 6
C resolveField() 13 59 14
A prepareAstArguments() 0 10 3
C prepareArgumentValue() 0 64 19
C getVariableReferenceArgumentValue() 0 24 11
A resolveObject() 0 21 4
C collectResult() 23 55 10
C resolveList() 0 66 10
B resolveComposite() 0 33 4
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\ErrorHandler\ErrorHandler;
15
use Youshido\GraphQL\Execution\ErrorHandler\Plugin\AddToErrorList;
16
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
17
use Youshido\GraphQL\Field\Field;
18
use Youshido\GraphQL\Field\FieldInterface;
19
use Youshido\GraphQL\Field\InputField;
20
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList as AstInputList;
21
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject as AstInputObject;
22
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal as AstLiteral;
23
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
24
use Youshido\GraphQL\Parser\Ast\Field as AstField;
25
use Youshido\GraphQL\Parser\Ast\FragmentReference;
26
use Youshido\GraphQL\Parser\Ast\Interfaces\FieldInterface as AstFieldInterface;
27
use Youshido\GraphQL\Parser\Ast\Mutation as AstMutation;
28
use Youshido\GraphQL\Parser\Ast\Query as AstQuery;
29
use Youshido\GraphQL\Parser\Ast\Query;
30
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
31
use Youshido\GraphQL\Parser\Parser;
32
use Youshido\GraphQL\Schema\AbstractSchema;
33
use Youshido\GraphQL\Type\AbstractType;
34
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
35
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
36
use Youshido\GraphQL\Type\ListType\AbstractListType;
37
use Youshido\GraphQL\Type\Object\AbstractObjectType;
38
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
39
use Youshido\GraphQL\Type\TypeMap;
40
use Youshido\GraphQL\Type\Union\AbstractUnionType;
41
use Youshido\GraphQL\Validator\RequestValidator\RequestValidator;
42
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
43
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
44
45
class Processor
46
{
47
48
    const TYPE_NAME_QUERY = '__typename';
49
50
    /** @var ExecutionContext */
51
    protected $executionContext;
52
53
    /** @var ResolveValidatorInterface */
54
    protected $resolveValidator;
55
56
    /** @var  array */
57
    protected $data;
58
59
    /** @var int */
60
    protected $maxComplexity;
61
62
    /** @var ErrorHandler  */
63
    protected $errorHandler;
64
65 61
    public function __construct(AbstractSchema $schema, ErrorHandler $errorHandler = null)
66
    {
67 61
        if (empty($this->executionContext)) {
68 61
            $this->executionContext = new ExecutionContext($schema);
69 61
            $this->executionContext->setContainer(new Container());
70
        }
71
72 61
        $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...
73
74 61
        if (null === $errorHandler) {
75 61
            $errorHandler = new ErrorHandler([
76 61
                new AddToErrorList()
77
            ]);
78
        }
79 61
        $this->errorHandler = $errorHandler;
80 61
    }
81
82 59
    public function processPayload($payload, $variables = [], $reducers = [])
83
    {
84 59
        $this->data = [];
85
86
        try {
87 59
            $this->parseAndCreateRequest($payload, $variables);
88
89 58
            if ($this->maxComplexity) {
90 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
91
            }
92
93 58
            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...
94 2
                $reducer = new Reducer();
95 2
                $reducer->reduceQuery($this->executionContext, $reducers);
96
            }
97
98 58
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
99 58
                if ($operationResult = $this->resolveQuery($query)) {
100 58
                    $this->data = array_merge($this->data, $operationResult);
101
                };
102
            }
103 5
        } catch (\Exception $e) {
104 5
            $this->errorHandler->handle($e, $this->executionContext);
105
        }
106
107 59
        return $this;
108
    }
109
110 58
    protected function resolveQuery(AstQuery $query)
111
    {
112 58
        $schema = $this->executionContext->getSchema();
113 58
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
114 58
        $field  = new Field([
115 58
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
116 58
            'type' => $type
117
        ]);
118
119 58
        if (self::TYPE_NAME_QUERY == $query->getName()) {
120 1
            return [$this->getAlias($query) => $type->getName()];
121
        }
122
123 58
        $this->resolveValidator->assetTypeHasField($type, $query);
124 58
        $value = $this->resolveField($field, $query);
125
126 58
        return [$this->getAlias($query) => $value];
127
    }
128
129 58
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
130
    {
131
        try {
132
            /** @var AbstractObjectType $type */
133 58
            $type        = $field->getType();
134 58
            $nonNullType = $type->getNullableType();
135
136 58
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
137 2
                return $nonNullType->getName();
138
            }
139
140 58
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
141
142 58
            $targetField = $nonNullType->getField($ast->getName());
143
144 58
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
145 57
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
146
147 52
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
148 52
                case TypeMap::KIND_ENUM:
149 52
                case TypeMap::KIND_SCALAR:
150 45
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
151 2
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation());
152
                    }
153
154 45
                    return $this->resolveScalar($targetField, $ast, $parentValue);
155
156 36 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...
157
                    /** @var $type AbstractObjectType */
158 27
                    if (!$ast instanceof AstQuery) {
159 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
160
                    }
161
162 27
                    return $this->resolveObject($targetField, $ast, $parentValue);
163
164 22
                case TypeMap::KIND_LIST:
165 19
                    return $this->resolveList($targetField, $ast, $parentValue);
166
167 6
                case TypeMap::KIND_UNION:
168 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...
169 6
                    if (!$ast instanceof AstQuery) {
170
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
171
                    }
172
173 6
                    return $this->resolveComposite($targetField, $ast, $parentValue);
174
175
                default:
176
                    throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind));
177
            }
178 17
        } catch (\Exception $e) {
179 17
            $this->errorHandler->handle($e, $this->executionContext);
180
181 17
            if ($fromObject) {
182 4
                throw $e;
183
            }
184
185 15
            return null;
186
        }
187
    }
188
189 58
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
190
    {
191 58
        foreach ($query->getArguments() as $astArgument) {
192 33
            if ($field->hasArgument($astArgument->getName())) {
193 33
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();
194
195 33
                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
196
            }
197
        }
198 57
    }
199
200 33
    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
201
    {
202 33
        switch ($argumentType->getKind()) {
203 33
            case TypeMap::KIND_LIST:
204
                /** @var $argumentType AbstractListType */
205 7
                $result = [];
206 7
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
207 5
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
208 5
                    foreach ($list as $item) {
209 5
                        $result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request);
210
                    }
211
                } else {
212 2
                    if ($argumentValue instanceof VariableReference) {
213 2
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
214
                    }
215
                }
216
217 5
                return $result;
218
219 32
            case TypeMap::KIND_INPUT_OBJECT:
220
                /** @var $argumentType AbstractInputObjectType */
221 6
                $result = [];
222 6
                if ($argumentValue instanceof AstInputObject) {
223 5
                    foreach ($argumentType->getFields() as $field) {
224
                        /** @var $field Field */
225 5
                        if ($field->getConfig()->has('defaultValue')) {
226 5
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
227
                        }
228
                    }
229 5
                    foreach ($argumentValue->getValue() as $key => $item) {
230 5
                        if ($argumentType->hasField($key)) {
231 5
                            $result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request);
232
                        } else {
233 5
                            $result[$key] = $item;
234
                        }
235
                    }
236
                } else {
237 2
                    if ($argumentValue instanceof VariableReference) {
238
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
239
                    } else {
240 2
                        if (is_array($argumentValue)) {
241 1
                            return $argumentValue;
242
                        }
243
                    }
244
                }
245
246 6
                return $result;
247
248 31
            case TypeMap::KIND_SCALAR:
249 3
            case TypeMap::KIND_ENUM:
250
                /** @var $argumentValue AstLiteral|VariableReference */
251 31
                if ($argumentValue instanceof VariableReference) {
252 6
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
253
                } else {
254 27
                    if ($argumentValue instanceof AstLiteral) {
255 19
                        return $argumentValue->getValue();
256
                    } else {
257 9
                        return $argumentValue;
258
                    }
259
                }
260
        }
261
262
        throw new ResolveException('Argument type not supported');
263
    }
264
265 8
    private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request)
266
    {
267 8
        $variable = $variableReference->getVariable();
268 8
        if ($argumentType->getKind() === TypeMap::KIND_LIST) {
269
            if (
270 2
                (!$variable->isArray() && !is_array($variable->getValue())) ||
271 2
                ($variable->getTypeName() !== $argumentType->getNamedType()->getNullableType()->getName()) ||
272 2
                ($argumentType->getNamedType()->getKind() === TypeMap::KIND_NON_NULL && $variable->isArrayElementNullable())
273
            ) {
274 2
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
275
            }
276
        } else {
277 6
            if ($variable->getTypeName() !== $argumentType->getName()) {
278 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()), $variable->getLocation());
279
            }
280
        }
281
282 7
        $requestValue = $request->getVariable($variable->getName());
283 7
        if ((null === $requestValue && $variable->isNullable()) && !$request->hasVariable($variable->getName())) {
284
            throw new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()), $variable->getLocation());
285
        }
286
287 7
        return $requestValue;
288
    }
289
290 32
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
291
    {
292 32
        $resolvedValue = $parentValue;
293 32
        if (!$fromUnion) {
294 27
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
295
        }
296
297 32
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
298
299 32
        if (null === $resolvedValue) {
300 5
            return null;
301
        }
302
        /** @var AbstractObjectType $type */
303 31
        $type = $field->getType()->getNullableType();
304
305
        try {
306 31
            return $this->collectResult($field, $type, $ast, $resolvedValue);
307 4
        } catch (\Exception $e) {
308 4
            return null;
309
        }
310
    }
311
312
    /**
313
     * @param FieldInterface     $field
314
     * @param AbstractObjectType $type
315
     * @param AstFieldInterface  $ast
316
     * @param                    $resolvedValue
317
     * @return array
318
     */
319 31
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
320
    {
321 31
        $result = [];
322
323 31
        foreach ($ast->getFields() as $astField) {
324
            switch (true) {
325 31
                case $astField instanceof TypedFragmentReference:
326 2
                    $astName  = $astField->getTypeName();
327 2
                    $typeName = $type->getName();
328
329 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...
330 2
                        foreach ($type->getInterfaces() as $interface) {
331 1
                            if ($interface->getName() === $astName) {
332
                                $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...
333
334 1
                                break;
335
                            }
336
                        }
337
338 2
                        continue;
339
                    }
340
341 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...
342
343 2
                    break;
344
345 31
                case $astField instanceof FragmentReference:
346 4
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
347 4
                    $astFragmentModel = $astFragment->getModel();
348 4
                    $typeName         = $type->getName();
349
350 4 View Code Duplication
                    if ($typeName !== $astFragmentModel) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
351 1
                        foreach ($type->getInterfaces() as $interface) {
352 1
                            if ($interface->getName() === $astFragmentModel) {
353 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...
354
355 1
                                break;
356
                            }
357
358
                        }
359
360 1
                        continue;
361
                    }
362
363 4
                    $result = array_merge($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astFragment is of type object<Youshido\GraphQL\Parser\Ast\Fragment>|null, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
364
365 4
                    break;
366
367
                default:
368 31
                    $result[$this->getAlias($astField)] = $this->resolveField($field, $astField, $resolvedValue, true);
369
            }
370
        }
371
372 31
        return $result;
373
    }
374
375 46
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
376
    {
377 46
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
378
379 46
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
380
381
        /** @var AbstractScalarType $type */
382 45
        $type = $field->getType()->getNullableType();
383
384 45
        return $type->serialize($resolvedValue);
385
    }
386
387 19
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
388
    {
389
        /** @var AstQuery $ast */
390 19
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
391
392 19
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
393
394 17
        if (null === $resolvedValue) {
395 5
            return null;
396
        }
397
398
        /** @var AbstractListType $type */
399 16
        $type     = $field->getType()->getNullableType();
400 16
        $itemType = $type->getNamedType();
401
402 16
        $fakeAst = clone $ast;
403 16
        if ($fakeAst instanceof AstQuery) {
404 15
            $fakeAst->setArguments([]);
405
        }
406
407 16
        $fakeField = new Field([
408 16
            'name' => $field->getName(),
409 16
            'type' => $itemType,
410 16
            'args' => $field->getArguments(),
411
        ]);
412
413 16
        $result = [];
414 16
        foreach ($resolvedValue as $resolvedValueItem) {
415
            try {
416 15
                $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
417 15
                    return $resolvedValueItem;
418 15
                });
419
420 15
                switch ($itemType->getNullableType()->getKind()) {
421 15
                    case TypeMap::KIND_ENUM:
422 14
                    case TypeMap::KIND_SCALAR:
423 5
                        $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
424
425 4
                        break;
426
427
428 11
                    case TypeMap::KIND_OBJECT:
429 9
                        $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
430
431 9
                        break;
432
433 3
                    case TypeMap::KIND_UNION:
434 3
                    case TypeMap::KIND_INTERFACE:
435 3
                        $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
436
437 3
                        break;
438
439
                    default:
440 14
                        $value = null;
441
                }
442 1
            } catch (\Exception $e) {
443 1
                $this->executionContext->addError($e);
444
445 1
                $value = null;
446
            }
447
448 15
            $result[] = $value;
449
        }
450
451 16
        return $result;
452
    }
453
454 7
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
455
    {
456
        /** @var AstQuery $ast */
457 7
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
458
459 7
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
460
461 7
        if (null === $resolvedValue) {
462
            return null;
463
        }
464
465
        /** @var AbstractUnionType $type */
466 7
        $type         = $field->getType()->getNullableType();
467 7
        $resolvedType = $type->resolveType($resolvedValue);
468
469 7
        if (!$resolvedType) {
470
            throw new ResolveException('Resolving function must return type');
471
        }
472
473 7
        if ($type instanceof AbstractInterfaceType) {
474 6
            $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
475
        } else {
476 1
            $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
477
        }
478
479 7
        $fakeField = new Field([
480 7
            'name' => $field->getName(),
481 7
            'type' => $resolvedType,
482 7
            'args' => $field->getArguments(),
483
        ]);
484
485 7
        return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
486
    }
487
488 59
    protected function parseAndCreateRequest($payload, $variables = [])
489
    {
490 59
        if (empty($payload)) {
491 1
            throw new \InvalidArgumentException('Must provide an operation.');
492
        }
493
494 59
        $parser  = new Parser();
495 59
        $request = new Request($parser->parse($payload), $variables);
496
497 59
        (new RequestValidator())->validate($request);
498
499 58
        $this->executionContext->setRequest($request);
500 58
    }
501
502 52
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
503
    {
504
        /** @var AstQuery|AstField $ast */
505 52
        $arguments = $this->parseArgumentsValues($field, $ast);
506 52
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
507
508 52
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
509
    }
510
511 52
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
512
    {
513 52
        $values   = [];
514 52
        $defaults = [];
515
516 52
        foreach ($field->getArguments() as $argument) {
517
            /** @var $argument InputField */
518 37
            if ($argument->getConfig()->has('defaultValue')) {
519 37
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
520
            }
521
        }
522
523 52
        foreach ($ast->getArguments() as $astArgument) {
524 29
            $argument     = $field->getArgument($astArgument->getName());
525 29
            $argumentType = $argument->getType()->getNullableType();
526
527 29
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
528
529 29
            if (isset($defaults[$argument->getName()])) {
530 29
                unset($defaults[$argument->getName()]);
531
            }
532
        }
533
534 52
        return array_merge($values, $defaults);
535
    }
536
537 58
    private function getAlias(AstFieldInterface $ast)
538
    {
539 58
        return $ast->getAlias() ?: $ast->getName();
540
    }
541
542 52
    protected function createResolveInfo(FieldInterface $field, array $astFields)
543
    {
544 52
        return new ResolveInfo($field, $astFields, $this->executionContext);
545
    }
546
547
548
    /**
549
     * You can access ExecutionContext to check errors and inject dependencies
550
     *
551
     * @return ExecutionContext
552
     */
553 11
    public function getExecutionContext()
554
    {
555 11
        return $this->executionContext;
556
    }
557
558 59
    public function getResponseData()
559
    {
560 59
        $result = [];
561
562 59
        if (!empty($this->data)) {
563 58
            $result['data'] = $this->data;
564
        }
565
566 59
        if ($this->executionContext->hasErrors()) {
567 19
            $result['errors'] = $this->executionContext->getErrorsArray();
568
        }
569
570 59
        return $result;
571
    }
572
573
    /**
574
     * @return int
575
     */
576
    public function getMaxComplexity()
577
    {
578
        return $this->maxComplexity;
579
    }
580
581
    /**
582
     * @param int $maxComplexity
583
     */
584 1
    public function setMaxComplexity($maxComplexity)
585
    {
586 1
        $this->maxComplexity = $maxComplexity;
587 1
    }
588
589
}
590