Completed
Pull Request — master (#146)
by
unknown
03:57
created

Processor   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 626
Duplicated Lines 5.59 %

Coupling/Cohesion

Components 1
Dependencies 35

Test Coverage

Coverage 95.24%

Importance

Changes 0
Metric Value
wmc 123
lcom 1
cbo 35
dl 35
loc 626
ccs 280
cts 294
cp 0.9524
rs 1.2365
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 3
F processPayload() 0 47 12
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() 22 54 11
A deferredResolve() 0 18 4
A resolveScalar() 0 12 1
C resolveList() 0 66 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\ErrorHandler\ErrorHandler;
15
use Youshido\GraphQL\Execution\ErrorHandler\Plugin\DefaultErrorHandler;
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\TypedFragmentReference;
30
use Youshido\GraphQL\Parser\Parser;
31
use Youshido\GraphQL\Schema\AbstractSchema;
32
use Youshido\GraphQL\Type\AbstractType;
33
use Youshido\GraphQL\Type\Enum\AbstractEnumType;
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
    /** @var array DeferredResult[] */
66
    protected $deferredResultsLeaf = [];
67
68
    /** @var array DeferredResult[] */
69
    protected $deferredResultsComplex = [];
70
71 70
    public function __construct(AbstractSchema $schema, ErrorHandler $errorHandler = null)
72
    {
73 70
        if (empty($this->executionContext)) {
74 70
            $this->executionContext = new ExecutionContext($schema);
75 70
            $this->executionContext->setContainer(new Container());
76
        }
77
78 70
        $this->resolveValidator = new ResolveValidator($this->executionContext);
0 ignored issues
show
Unused Code introduced by
The call to ResolveValidator::__construct() has too many arguments starting with $this->executionContext.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
79
80 70
        if (null === $errorHandler) {
81 70
            $errorHandler = new ErrorHandler([
82 70
                new DefaultErrorHandler()
83
            ]);
84
        }
85 70
        $this->errorHandler = $errorHandler;
86 70
    }
87
88 68
    public function processPayload($payload, $variables = [], $reducers = [])
89
    {
90 68
        $this->data = [];
91
92
        try {
93 68
            $this->parseAndCreateRequest($payload, $variables);
94
95 67
            if ($this->maxComplexity) {
96 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
97
            }
98
99 67
            if ($reducers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $reducers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
100 2
                $reducer = new Reducer();
101 2
                $reducer->reduceQuery($this->executionContext, $reducers);
102
            }
103
104 67
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
105 67
                if ($operationResult = $this->resolveQuery($query)) {
106 67
                    $this->data = array_replace_recursive($this->data, $operationResult);
107
                };
108
            }
109
110
            // If the processor found any deferred results, resolve them now.
111 67
            if (!empty($this->data) && (!empty($this->deferredResultsLeaf) || !empty($this->deferredResultsComplex))) {
112
              try {
113 4
                  while ($deferredResolver = array_shift($this->deferredResultsComplex)) {
114 4
                      $deferredResolver->resolve();
115
                  }
116
117
                  // Deferred scalar and enum fields should be resolved last to
118
                  // pick up as many as possible for a single batch.
119 4
                  while ($deferredResolver = array_shift($this->deferredResultsLeaf)) {
120
                      $deferredResolver->resolve();
121
                  }
122
              } catch (\Exception $e) {
123
                  $this->executionContext->addError($e);
124 4
              } finally {
125 67
                  $this->data = static::unpackDeferredResults($this->data);
126
              }
127
            }
128
129 5
        } catch (\Exception $e) {
130 5
            $this->errorHandler->handle($e, $this->executionContext);
131
        }
132
133 68
        return $this;
134
    }
135
136
    /**
137
     * Unpack results stored inside deferred resolvers.
138
     *
139
     * @param mixed $result
140
     *   The result ree.
141
     *
142
     * @return mixed
143
     *   The unpacked result.
144
     */
145 4
    public static function unpackDeferredResults($result)
146
    {
147 4
        while ($result instanceof DeferredResult) {
148 4
            $result = $result->result;
149
        }
150
151 4
        if (is_array($result)) {
152 4
            foreach ($result as $key => $value) {
153 4
                $result[$key] = static::unpackDeferredResults($value);
154
            }
155
        }
156
157 4
        return $result;
158
    }
159
160 67
    protected function resolveQuery(AstQuery $query)
161
    {
162 67
        $schema = $this->executionContext->getSchema();
163 67
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
164 67
        $field  = new Field([
165 67
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
166 67
            'type' => $type
167
        ]);
168
169 67
        if (self::TYPE_NAME_QUERY == $query->getName()) {
170 1
            return [$this->getAlias($query) => $type->getName()];
171
        }
172
173 67
        $this->resolveValidator->assetTypeHasField($type, $query);
174 67
        $value = $this->resolveField($field, $query);
175
176 67
        return [$this->getAlias($query) => $value];
177
    }
178
179 67
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
180
    {
181
        try {
182
            /** @var AbstractObjectType $type */
183 67
            $type        = $field->getType();
184 67
            $nonNullType = $type->getNullableType();
185
186 67
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
187 3
                return $nonNullType->getName();
188
            }
189
190 67
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
191
192 67
            $targetField = $nonNullType->getField($ast->getName());
193
194 67
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
195 66
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
196
197 61
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
198 61
                case TypeMap::KIND_ENUM:
199 60
                case TypeMap::KIND_SCALAR:
200 54
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
201 2
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation());
202
                    }
203
204 54
                    return $this->resolveScalar($targetField, $ast, $parentValue);
205
206 44 View Code Duplication
                case TypeMap::KIND_OBJECT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
207
                    /** @var $type AbstractObjectType */
208 30
                    if (!$ast instanceof AstQuery) {
209 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
210
                    }
211
212 30
                    return $this->resolveObject($targetField, $ast, $parentValue);
213
214 30
                case TypeMap::KIND_LIST:
215 27
                    return $this->resolveList($targetField, $ast, $parentValue);
216
217 6
                case TypeMap::KIND_UNION:
218 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...
219 6
                    if (!$ast instanceof AstQuery) {
220
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
221
                    }
222
223 6
                    return $this->resolveComposite($targetField, $ast, $parentValue);
224
225
                default:
226
                    throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind));
227
            }
228 17
        } catch (\Exception $e) {
229 17
            $this->errorHandler->handle($e, $this->executionContext);
230
231 17
            if ($fromObject) {
232 4
                throw $e;
233
            }
234
235 15
            return null;
236
        }
237
    }
238
239 67
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
240
    {
241 67
        foreach ($query->getArguments() as $astArgument) {
242 38
            if ($field->hasArgument($astArgument->getName())) {
243 38
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();
244
245 38
                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
246
            }
247
        }
248 66
    }
249
250 38
    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
251
    {
252 38
        switch ($argumentType->getKind()) {
253 38
            case TypeMap::KIND_LIST:
254
                /** @var $argumentType AbstractListType */
255 12
                $result = [];
256 12
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
257 9
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
258 9
                    foreach ($list as $item) {
259 9
                        $result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request);
260
                    }
261
                } else {
262 3
                    if ($argumentValue instanceof VariableReference) {
263 3
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
264
                    }
265
                }
266
267 9
                return $result;
268
269 37
            case TypeMap::KIND_INPUT_OBJECT:
270
                /** @var $argumentType AbstractInputObjectType */
271 6
                $result = [];
272 6
                if ($argumentValue instanceof AstInputObject) {
273 5
                    foreach ($argumentType->getFields() as $field) {
274
                        /** @var $field Field */
275 5
                        if ($field->getConfig()->has('defaultValue')) {
276 5
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
277
                        }
278
                    }
279 5
                    foreach ($argumentValue->getValue() as $key => $item) {
280 5
                        if ($argumentType->hasField($key)) {
281 5
                            $result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request);
282
                        } else {
283 5
                            $result[$key] = $item;
284
                        }
285
                    }
286
                } else {
287 2
                    if ($argumentValue instanceof VariableReference) {
288
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
289
                    } else {
290 2
                        if (is_array($argumentValue)) {
291 1
                            return $argumentValue;
292
                        }
293
                    }
294
                }
295
296 6
                return $result;
297
298 36
            case TypeMap::KIND_SCALAR:
299 4
            case TypeMap::KIND_ENUM:
300
                /** @var $argumentValue AstLiteral|VariableReference */
301 36
                if ($argumentValue instanceof VariableReference) {
302 7
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
303
                } else {
304 31
                    if ($argumentValue instanceof AstLiteral) {
305 19
                        return $argumentValue->getValue();
306
                    } else {
307 13
                        return $argumentValue;
308
                    }
309
                }
310
        }
311
312
        throw new ResolveException('Argument type not supported');
313
    }
314
315 9
    private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request)
316
    {
317 9
        $variable = $variableReference->getVariable();
318 9
        if ($argumentType->getKind() === TypeMap::KIND_LIST) {
319
            if (
320 3
                (!$variable->isArray() && !is_array($variable->getValue())) ||
321 3
                ($variable->getTypeName() !== $argumentType->getNamedType()->getNullableType()->getName()) ||
322 3
                ($argumentType->getNamedType()->getKind() === TypeMap::KIND_NON_NULL && $variable->isArrayElementNullable())
323
            ) {
324 3
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
325
            }
326
        } else {
327 7
            if ($variable->getTypeName() !== $argumentType->getName()) {
328 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()), $variable->getLocation());
329
            }
330
        }
331
332 8
        $requestValue = $request->getVariable($variable->getName());
333 8
        if ((null === $requestValue && $variable->isNullable()) && !$request->hasVariable($variable->getName())) {
334
            throw new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()), $variable->getLocation());
335
        }
336
337 8
        return $requestValue;
338
    }
339
340
341
    /**
342
     * @param FieldInterface     $field
343
     * @param AbstractObjectType $type
344
     * @param AstFieldInterface  $ast
345
     * @param                    $resolvedValue
346
     * @return array
347
     */
348 39
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
349
    {
350 39
        $result = [];
351
352 39
        foreach ($ast->getFields() as $astField) {
353
            switch (true) {
354 39
                case $astField instanceof TypedFragmentReference:
355 3
                    $astName  = $astField->getTypeName();
356 3
                    $typeName = $type->getName();
357
358 3 View Code Duplication
                    if ($typeName !== $astName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
359 3
                        foreach ($type->getInterfaces() as $interface) {
360 1
                            if ($interface->getName() === $astName) {
361
                                $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...
362
363 1
                                break;
364
                            }
365
                        }
366
367 3
                        continue 2;
368
                    }
369
370 3
                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astField is of type object<Youshido\GraphQL\...TypedFragmentReference>, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
371
372 3
                    break;
373
374 39
                case $astField instanceof FragmentReference:
375 7
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
376 7
                    $astFragmentModel = $astFragment->getModel();
377 7
                    $typeName         = $type->getName();
378
379 7 View Code Duplication
                    if ($typeName !== $astFragmentModel && $type->getInterfaces()) {
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...
380 1
                        foreach ($type->getInterfaces() as $interface) {
381 1
                            if ($interface->getName() === $astFragmentModel) {
382 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...
383
384 1
                                break;
385
                            }
386
                        }
387
388 1
                        continue 2;
389
                    }
390
391 7
                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astFragment is of type object<Youshido\GraphQL\Parser\Ast\Fragment>|null, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
392
393 7
                    break;
394
395
                default:
396 39
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
397
            }
398
        }
399
400 39
        return $result;
401
    }
402
403
    /**
404
     * Apply post-process callbacks to all deferred resolvers.
405
     */
406 61
    protected function deferredResolve($resolvedValue, FieldInterface $field, callable $callback) {
407 61
        if ($resolvedValue instanceof DeferredResolverInterface) {
408 4
            $deferredResult = new DeferredResult($resolvedValue, $callback);
409
410
            // Whenever we stumble upon a deferred resolver, add it to the queue
411
            // to be resolved later.
412 4
            $type = $field->getType()->getNamedType();
413 4
            if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) {
414
                array_push($this->deferredResultsLeaf, $deferredResult);
415
            } else {
416 4
                array_push($this->deferredResultsComplex, $deferredResult);
417
            }
418
419 4
            return $deferredResult;
420
        }
421
        // For simple values, invoke the callback immediately.
422 61
        return $callback($resolvedValue);
423
    }
424
425 55
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
426
    {
427 55
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
428
        return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) {
429 55
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
430
431
            /** @var AbstractScalarType $type */
432 54
            $type = $field->getType()->getNullableType();
433
434 54
            return $type->serialize($resolvedValue);
435 55
        });
436
    }
437
438 27
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
439
    {
440
        /** @var AstQuery $ast */
441 27
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
442
443
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
444 27
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
445
446 25
            if (null === $resolvedValue) {
447 7
                return null;
448
            }
449
450
            /** @var AbstractListType $type */
451 24
            $type     = $field->getType()->getNullableType();
452 24
            $itemType = $type->getNamedType();
453
454 24
            $fakeAst = clone $ast;
455 24
            if ($fakeAst instanceof AstQuery) {
456 23
                $fakeAst->setArguments([]);
457
            }
458
459 24
            $fakeField = new Field([
460 24
              'name' => $field->getName(),
461 24
              'type' => $itemType,
462 24
              'args' => $field->getArguments(),
463
            ]);
464
465 24
            $result = [];
466 24
            foreach ($resolvedValue as $resolvedValueItem) {
467
                try {
468
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
469 23
                        return $resolvedValueItem;
470 23
                    });
471
472 23
                    switch ($itemType->getNullableType()->getKind()) {
473 23
                        case TypeMap::KIND_ENUM:
474 22
                        case TypeMap::KIND_SCALAR:
475 5
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
476
477 4
                            break;
478
479
480 19
                        case TypeMap::KIND_OBJECT:
481 16
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
482
483 16
                            break;
484
485 4
                        case TypeMap::KIND_UNION:
486 3
                        case TypeMap::KIND_INTERFACE:
487 4
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
488
489 4
                            break;
490
491
                        default:
492 22
                            $value = null;
493
                    }
494 1
                } catch (\Exception $e) {
495 1
                    $this->errorHandler->handle($e, $this->executionContext);
496 1
                    $value = null;
497
                }
498 23
                $result[] = $value;
499
            }
500
501 24
            return $result;
502 27
        });
503
    }
504
505 40
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
506
    {
507 40
        $resolvedValue = $parentValue;
508 40
        if (!$fromUnion) {
509 34
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
510
        }
511
512
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
513 40
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
514
515 40
            if (null === $resolvedValue) {
516 7
                return null;
517
            }
518
            /** @var AbstractObjectType $type */
519 39
            $type = $field->getType()->getNullableType();
520
521
            try {
522 39
                return $this->collectResult($field, $type, $ast, $resolvedValue);
523 4
            } catch (\Exception $e) {
524 4
                return null;
525
            }
526 40
        });
527
    }
528
529 8
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
530
    {
531
        /** @var AstQuery $ast */
532 8
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
533 8
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
534 8
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
535
536 8
            if (null === $resolvedValue) {
537
                return null;
538
            }
539
540
            /** @var AbstractUnionType $type */
541 8
            $type         = $field->getType()->getNullableType();
542 8
            $resolveInfo = new ResolveInfo(
543 8
                $field,
544 8
                $ast instanceof AstQuery ? $ast->getFields() : [],
545 8
                $this->executionContext
546
            );
547 8
            $resolvedType = $type->resolveType($resolvedValue, $resolveInfo);
0 ignored issues
show
Unused Code introduced by
The call to AbstractUnionType::resolveType() has too many arguments starting with $resolveInfo.

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...
548
549 8
            if (!$resolvedType) {
550
                throw new ResolveException('Resolving function must return type');
551
            }
552
553 8
            if ($type instanceof AbstractInterfaceType) {
554 6
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
555
            } else {
556 2
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
557
            }
558
559 8
            $fakeField = new Field([
560 8
              'name' => $field->getName(),
561 8
              'type' => $resolvedType,
562 8
              'args' => $field->getArguments(),
563
            ]);
564
565 8
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
566 8
        });
567
    }
568
569 68
    protected function parseAndCreateRequest($payload, $variables = [])
570
    {
571 68
        if (empty($payload)) {
572 1
            throw new \InvalidArgumentException('Must provide an operation.');
573
        }
574
575 68
        $parser  = new Parser();
576 68
        $request = new Request($parser->parse($payload), $variables);
577
578 68
        (new RequestValidator())->validate($request);
579
580 67
        $this->executionContext->setRequest($request);
581 67
    }
582
583 61
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
584
    {
585
        /** @var AstQuery|AstField $ast */
586 61
        $arguments = $this->parseArgumentsValues($field, $ast);
587 61
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
588
589 61
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
590
    }
591
592 61
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
593
    {
594 61
        $values   = [];
595 61
        $defaults = [];
596
597 61
        foreach ($field->getArguments() as $argument) {
598
            /** @var $argument InputField */
599 44
            if ($argument->getConfig()->has('defaultValue')) {
600 44
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
601
            }
602
        }
603
604 61
        foreach ($ast->getArguments() as $astArgument) {
605 34
            $argument     = $field->getArgument($astArgument->getName());
606 34
            $argumentType = $argument->getType()->getNullableType();
607
608 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
609
610 34
            if (isset($defaults[$argument->getName()])) {
611 34
                unset($defaults[$argument->getName()]);
612
            }
613
        }
614
615 61
        return array_merge($values, $defaults);
616
    }
617
618 67
    private function getAlias(AstFieldInterface $ast)
619
    {
620 67
        return $ast->getAlias() ?: $ast->getName();
621
    }
622
623 61
    protected function createResolveInfo(FieldInterface $field, array $astFields)
624
    {
625 61
        return new ResolveInfo($field, $astFields, $this->executionContext);
626
    }
627
628
629
    /**
630
     * You can access ExecutionContext to check errors and inject dependencies
631
     *
632
     * @return ExecutionContext
633
     */
634 11
    public function getExecutionContext()
635
    {
636 11
        return $this->executionContext;
637
    }
638
639 67
    public function getResponseData()
640
    {
641 67
        $result = [];
642
643 67
        if (!empty($this->data)) {
644 66
            $result['data'] = $this->data;
645
        }
646
647 67
        if ($this->executionContext->hasErrors()) {
648 19
            $result['errors'] = $this->executionContext->getErrorsArray();
649
        }
650
651 67
        return $result;
652
    }
653
654
    /**
655
     * @return int
656
     */
657
    public function getMaxComplexity()
658
    {
659
        return $this->maxComplexity;
660
    }
661
662
    /**
663
     * @param int $maxComplexity
664
     */
665 1
    public function setMaxComplexity($maxComplexity)
666
    {
667 1
        $this->maxComplexity = $maxComplexity;
668 1
    }
669
670
}
671