Processor   F
last analyzed

Complexity

Total Complexity 121

Size/Duplication

Total Lines 621
Duplicated Lines 5.64 %

Coupling/Cohesion

Components 1
Dependencies 33

Test Coverage

Coverage 93.53%

Importance

Changes 0
Metric Value
wmc 121
lcom 1
cbo 33
dl 35
loc 621
ccs 318
cts 340
cp 0.9353
rs 1.979
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveQuery() 0 18 4
A __construct() 0 9 2
A unpackDeferredResults() 0 14 4
C resolveField() 13 59 14
A prepareAstArguments() 0 10 3
D prepareArgumentValue() 0 64 19
B getVariableReferenceArgumentValue() 0 24 11
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 47 12
B collectResult() 22 54 10
A deferredResolve() 0 21 4
C resolveList() 0 68 10
A resolveObject() 0 23 4
B resolveComposite() 0 39 5
A 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\Enum\AbstractEnumType;
32
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
33
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
34
use Youshido\GraphQL\Type\ListType\AbstractListType;
35
use Youshido\GraphQL\Type\Object\AbstractObjectType;
36
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
37
use Youshido\GraphQL\Type\TypeMap;
38
use Youshido\GraphQL\Type\Union\AbstractUnionType;
39
use Youshido\GraphQL\Validator\RequestValidator\RequestValidator;
40
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
41
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
42
43
class Processor
44
{
45
46
    const TYPE_NAME_QUERY = '__typename';
47
48
    /** @var ExecutionContext */
49
    protected $executionContext;
50
51
    /** @var ResolveValidatorInterface */
52
    protected $resolveValidator;
53
54
    /** @var  array */
55
    protected $data;
56
57
    /** @var int */
58
    protected $maxComplexity;
59
60
    /** @var array DeferredResult[] */
61
    protected $deferredResultsLeaf = [];
62
63
    /** @var array DeferredResult[] */
64
    protected $deferredResultsComplex = [];
65
66 71
    public function __construct(AbstractSchema $schema)
67
    {
68 71
        if (empty($this->executionContext)) {
69 71
            $this->executionContext = new ExecutionContext($schema);
70 71
            $this->executionContext->setContainer(new Container());
71 71
        }
72
73 71
        $this->resolveValidator = new ResolveValidator($this->executionContext);
74 71
    }
75
76 69
    public function processPayload($payload, $variables = [], $reducers = [])
77 1
    {
78 69
        $this->data = [];
79
80
        try {
81 69
            $this->parseAndCreateRequest($payload, $variables);
82
83 68
            if ($this->maxComplexity) {
84 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
85 1
            }
86
87 68
            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...
88 2
                $reducer = new Reducer();
89 2
                $reducer->reduceQuery($this->executionContext, $reducers);
90 2
            }
91
92 68
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
93 68
                if ($operationResult = $this->resolveQuery($query)) {
94 68
                    $this->data = array_replace_recursive($this->data, $operationResult);
95 68
                };
96 68
            }
97
98
            // If the processor found any deferred results, resolve them now.
99 68
            if (!empty($this->data) && (!empty($this->deferredResultsLeaf) || !empty($this->deferredResultsComplex))) {
100
              try {
101 4
                  while ($deferredResolver = array_shift($this->deferredResultsComplex)) {
102 4
                      $deferredResolver->resolve();
103 4
                  }
104
105
                  // Deferred scalar and enum fields should be resolved last to
106
                  // pick up as many as possible for a single batch.
107 4
                  while ($deferredResolver = array_shift($this->deferredResultsLeaf)) {
108
                      $deferredResolver->resolve();
109
                  }
110 4
              } catch (\Exception $e) {
111
                  $this->executionContext->addError($e);
112 4
              } finally {
113 4
                  $this->data = static::unpackDeferredResults($this->data);
114 4
              }
115 4
            }
116
117 69
        } catch (\Exception $e) {
118 5
            $this->executionContext->addError($e);
119
        }
120
121 69
        return $this;
122
    }
123
124
    /**
125
     * Unpack results stored inside deferred resolvers.
126
     *
127
     * @param mixed $result
128
     *   The result ree.
129
     *
130
     * @return mixed
131
     *   The unpacked result.
132
     */
133 4
    public static function unpackDeferredResults($result)
134
    {
135 4
        while ($result instanceof DeferredResult) {
136 4
            $result = $result->result;
137 4
        }
138
139 4
        if (is_array($result)) {
140 4
            foreach ($result as $key => $value) {
141 4
                $result[$key] = static::unpackDeferredResults($value);
142 4
            }
143 4
        }
144
145 4
        return $result;
146
    }
147
148 68
    protected function resolveQuery(AstQuery $query)
149
    {
150 68
        $schema = $this->executionContext->getSchema();
151 68
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
152 68
        $field  = new Field([
153 68
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
154
            'type' => $type
155 68
        ]);
156
157 68
        if (self::TYPE_NAME_QUERY == $query->getName()) {
158 1
            return [$this->getAlias($query) => $type->getName()];
159
        }
160
161 68
        $this->resolveValidator->assetTypeHasField($type, $query);
162 68
        $value = $this->resolveField($field, $query);
163
164 68
        return [$this->getAlias($query) => $value];
165
    }
166
167 68
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
168
    {
169
        try {
170
            /** @var AbstractObjectType $type */
171 68
            $type        = $field->getType();
172 68
            $nonNullType = $type->getNullableType();
173
174 68
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
175 2
                return $nonNullType->getName();
176
            }
177
178 68
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
179
180 68
            $targetField = $this->executionContext->getField($nonNullType, $ast->getName());
0 ignored issues
show
Compatibility introduced by
$nonNullType of type object<Youshido\GraphQL\Type\AbstractType> is not a sub-type of object<Youshido\GraphQL\...ect\AbstractObjectType>. It seems like you assume a child class of the class Youshido\GraphQL\Type\AbstractType to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
359
360 2
                    break;
361
362 40
                case $astField instanceof FragmentReference:
363 8
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
364 8
                    $astFragmentModel = $astFragment->getModel();
365 8
                    $typeName         = $type->getName();
366
367 8 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...
368 2
                        foreach ($type->getInterfaces() as $interface) {
369 1
                            if ($interface->getName() === $astFragmentModel) {
370 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...
371
372 1
                                break;
373
                            }
374 2
                        }
375
376 2
                        continue 2;
377
                    }
378
379 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...
380
381 7
                    break;
382
383 39
                default:
384 39
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
385 39
            }
386 40
        }
387
388 40
        return $result;
389
    }
390
391
    /**
392
     * Apply post-process callbacks to all deferred resolvers.
393
     */
394 62
    protected function deferredResolve($resolvedValue, FieldInterface $field, callable $callback) {
395 62
        if ($resolvedValue instanceof DeferredResolverInterface) {
396
            $deferredResult = new DeferredResult($resolvedValue, function ($resolvedValue) use ($field, $callback) {
397
                // Allow nested deferred resolvers.
398 4
                return $this->deferredResolve($resolvedValue, $field, $callback);
399 4
            });
400
401
            // Whenever we stumble upon a deferred resolver, add it to the queue
402
            // to be resolved later.
403 4
            $type = $field->getType()->getNamedType();
404 4
            if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) {
405
                array_push($this->deferredResultsLeaf, $deferredResult);
406
            } else {
407 4
                array_push($this->deferredResultsComplex, $deferredResult);
408
            }
409
410 4
            return $deferredResult;
411
        }
412
        // For simple values, invoke the callback immediately.
413 62
        return $callback($resolvedValue);
414
    }
415
416 55
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
417
    {
418 55
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
419
        return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) {
420 55
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
421
422
            /** @var AbstractScalarType $type */
423 54
            $type = $field->getType()->getNullableType();
424
425 54
            return $type->serialize($resolvedValue);
426 55
        });
427
    }
428
429 28
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
430
    {
431
        /** @var AstQuery $ast */
432 28
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
433
434
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
435 28
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
436
437 26
            if (null === $resolvedValue) {
438 8
                return null;
439
            }
440
441
            /** @var AbstractListType $type */
442 25
            $type     = $field->getType()->getNullableType();
443 25
            $itemType = $type->getNamedType();
444
445 25
            $fakeAst = clone $ast;
446 25
            if ($fakeAst instanceof AstQuery) {
447 24
                $fakeAst->setArguments([]);
448 24
            }
449
450 25
            $fakeField = new Field([
451 25
              'name' => $field->getName(),
452 25
              'type' => $itemType,
453 25
              'args' => $field->getArguments(),
454 25
            ]);
455
456 25
            $result = [];
457 25
            foreach ($resolvedValue as $resolvedValueItem) {
458
                try {
459
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
460 24
                        return $resolvedValueItem;
461 24
                    });
462
463 24
                    switch ($itemType->getNullableType()->getKind()) {
464 24
                        case TypeMap::KIND_ENUM:
465 24
                        case TypeMap::KIND_SCALAR:
466 6
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
467
468 5
                            break;
469
470
471 20
                        case TypeMap::KIND_OBJECT:
472 17
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
473
474 17
                            break;
475
476 4
                        case TypeMap::KIND_UNION:
477 4
                        case TypeMap::KIND_INTERFACE:
478 4
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
479
480 4
                            break;
481
482
                        default:
483
                            $value = null;
484 23
                    }
485 24
                } catch (\Exception $e) {
486 1
                    $this->executionContext->addError($e);
487
488 1
                    $value = null;
489
                }
490
491 24
                $result[] = $value;
492 25
            }
493
494 25
            return $result;
495 28
        });
496
    }
497
498 41
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
499
    {
500 41
        $resolvedValue = $parentValue;
501 41
        if (!$fromUnion) {
502 35
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
503 35
        }
504
505
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
506 41
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
507
508 41
            if (null === $resolvedValue) {
509 8
                return null;
510
            }
511
            /** @var AbstractObjectType $type */
512 40
            $type = $field->getType()->getNullableType();
513
514
            try {
515 40
                return $this->collectResult($field, $type, $ast, $resolvedValue);
516 4
            } catch (\Exception $e) {
517 4
                return null;
518
            }
519 41
        });
520
    }
521
522 8
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
523
    {
524
        /** @var AstQuery $ast */
525 8
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
526 8
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
527 8
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
528
529 8
            if (null === $resolvedValue) {
530
                return null;
531
            }
532
533
            /** @var AbstractUnionType $type */
534 8
            $type         = $field->getType()->getNullableType();
535 8
            $resolveInfo = new ResolveInfo(
536 8
                $field,
537 8
                $ast instanceof AstQuery ? $ast->getFields() : [],
538 8
                $this->executionContext
539 8
            );
540 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...
541
542 8
            if (!$resolvedType) {
543
                throw new ResolveException('Resolving function must return type');
544
            }
545
546 8
            if ($type instanceof AbstractInterfaceType) {
547 6
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
548 6
            } else {
549 2
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
550
            }
551
552 8
            $fakeField = new Field([
553 8
              'name' => $field->getName(),
554 8
              'type' => $resolvedType,
555 8
              'args' => $field->getArguments(),
556 8
            ]);
557
558 8
            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
559 8
        });
560
    }
561
562 69
    protected function parseAndCreateRequest($payload, $variables = [])
563
    {
564 69
        if (empty($payload)) {
565 1
            throw new \InvalidArgumentException('Must provide an operation.');
566
        }
567
568 69
        $parser  = new Parser();
569 69
        $request = new Request($parser->parse($payload), $variables);
570
571 69
        (new RequestValidator())->validate($request);
572
573 68
        $this->executionContext->setRequest($request);
574 68
    }
575
576 62
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
577
    {
578
        /** @var AstQuery|AstField $ast */
579 62
        $arguments = $this->parseArgumentsValues($field, $ast);
580 62
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
581
582 62
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
583
    }
584
585 62
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
586
    {
587 62
        $values   = [];
588 62
        $defaults = [];
589
590 62
        foreach ($field->getArguments() as $argument) {
591
            /** @var $argument InputField */
592 45
            if ($argument->getConfig()->has('defaultValue')) {
593 9
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
594 9
            }
595 62
        }
596
597 62
        foreach ($ast->getArguments() as $astArgument) {
598 34
            $argument     = $field->getArgument($astArgument->getName());
599 34
            $argumentType = $argument->getType()->getNullableType();
600
601 34
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
602
603 34
            if (isset($defaults[$argument->getName()])) {
604 3
                unset($defaults[$argument->getName()]);
605 3
            }
606 62
        }
607
608 62
        return array_merge($values, $defaults);
609
    }
610
611 68
    private function getAlias(AstFieldInterface $ast)
612
    {
613 68
        return $ast->getAlias() ?: $ast->getName();
614
    }
615
616 62
    protected function createResolveInfo(FieldInterface $field, array $astFields)
617
    {
618 62
        return new ResolveInfo($field, $astFields, $this->executionContext);
619
    }
620
621
622
    /**
623
     * You can access ExecutionContext to check errors and inject dependencies
624
     *
625
     * @return ExecutionContext
626
     */
627 11
    public function getExecutionContext()
628
    {
629 11
        return $this->executionContext;
630
    }
631
632 68
    public function getResponseData()
633
    {
634 68
        $result = [];
635
636 68
        if (!empty($this->data)) {
637 67
            $result['data'] = $this->data;
638 67
        }
639
640 68
        if ($this->executionContext->hasErrors()) {
641 19
            $result['errors'] = $this->executionContext->getErrorsArray();
642 19
        }
643
644 68
        return $result;
645
    }
646
647
    /**
648
     * @return int
649
     */
650
    public function getMaxComplexity()
651
    {
652
        return $this->maxComplexity;
653
    }
654
655
    /**
656
     * @param int $maxComplexity
657
     */
658 1
    public function setMaxComplexity($maxComplexity)
659
    {
660 1
        $this->maxComplexity = $maxComplexity;
661 1
    }
662
663
}
664