Completed
Pull Request — master (#181)
by
unknown
02:23
created

Processor::getMaxComplexity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * Date: 03.11.16
4
 *
5
 * @author Portey Vasil <[email protected]>
6
 */
7
8
namespace Youshido\GraphQL\Execution;
9
10
11
use Youshido\GraphQL\Exception\ResolveException;
12
use Youshido\GraphQL\Execution\Container\Container;
13
use Youshido\GraphQL\Execution\Context\ExecutionContext;
14
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
15
use Youshido\GraphQL\Field\Field;
16
use Youshido\GraphQL\Field\FieldInterface;
17
use Youshido\GraphQL\Field\InputField;
18
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList as AstInputList;
19
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject as AstInputObject;
20
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal as AstLiteral;
21
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
22
use Youshido\GraphQL\Parser\Ast\Field as AstField;
23
use Youshido\GraphQL\Parser\Ast\FragmentReference;
24
use Youshido\GraphQL\Parser\Ast\Interfaces\FieldInterface as AstFieldInterface;
25
use Youshido\GraphQL\Parser\Ast\Mutation as AstMutation;
26
use Youshido\GraphQL\Parser\Ast\Query as AstQuery;
27
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
28
use Youshido\GraphQL\Parser\Parser;
29
use Youshido\GraphQL\Schema\AbstractSchema;
30
use Youshido\GraphQL\Type\AbstractType;
31
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
32
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
33
use Youshido\GraphQL\Type\ListType\AbstractListType;
34
use Youshido\GraphQL\Type\Object\AbstractObjectType;
35
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
36
use Youshido\GraphQL\Type\TypeMap;
37
use Youshido\GraphQL\Type\Union\AbstractUnionType;
38
use Youshido\GraphQL\Validator\RequestValidator\RequestValidator;
39
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
40
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
41
42
class Processor
43
{
44
45
    const TYPE_NAME_QUERY = '__typename';
46
47
    /** @var ExecutionContext */
48
    protected $executionContext;
49
50
    /** @var ResolveValidatorInterface */
51
    protected $resolveValidator;
52
53
    /** @var  array */
54
    protected $data;
55
56
    /** @var int */
57
    protected $maxComplexity;
58
59
    /** @var array DeferredResult[] */
60
    protected $deferredResults = [];
61
62 68
    public function __construct(AbstractSchema $schema)
63
    {
64 68
        if (empty($this->executionContext)) {
65 68
            $this->executionContext = new ExecutionContext($schema);
66 68
            $this->executionContext->setContainer(new Container());
67
        }
68
69 68
        $this->resolveValidator = new ResolveValidator($this->executionContext);
0 ignored issues
show
Unused Code introduced by
The call to ResolveValidator::__construct() has too many arguments starting with $this->executionContext.

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

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

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

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

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

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

Loading history...
84 2
                $reducer = new Reducer();
85 2
                $reducer->reduceQuery($this->executionContext, $reducers);
86
            }
87
88 65
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
89 65
                if ($operationResult = $this->resolveQuery($query)) {
90 65
                    $this->data = array_merge($this->data, $operationResult);
91
                };
92
            }
93
94
            // If the processor found any deferred results, resolve them now.
95 65
            if (!empty($this->data) && $this->deferredResults) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredResults of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
96
              try {
97 4
                  while ($deferredResolver = array_shift($this->deferredResults)) {
98 4
                      $deferredResolver->resolve();
99
                  }
100
              } catch (\Exception $e) {
101
                  $this->executionContext->addError($e);
102 4
              } finally {
103 65
                  $this->data = static::unpackDeferredResults($this->data);
104
              }
105
            }
106
107 5
        } catch (\Exception $e) {
108 5
            $this->executionContext->addError($e);
109
        }
110
111 66
        return $this;
112
    }
113
114
    /**
115
     * Unpack results stored inside deferred resolvers.
116
     *
117
     * @param mixed $result
118
     *   The result ree.
119
     *
120
     * @return mixed
121
     *   The unpacked result.
122
     */
123 4
    public static function unpackDeferredResults($result)
124
    {
125 4
        while ($result instanceof DeferredResult) {
126 4
            $result = $result->result;
127
        }
128
129 4
        if (is_array($result)) {
130 4
            foreach ($result as $key => $value) {
131 4
                $result[$key] = static::unpackDeferredResults($value);
132
            }
133
        }
134
135 4
        return $result;
136
    }
137
138 65
    protected function resolveQuery(AstQuery $query)
139
    {
140 65
        $schema = $this->executionContext->getSchema();
141 65
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
142 65
        $field  = new Field([
143 65
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
144 65
            'type' => $type
145
        ]);
146
147 65
        if (self::TYPE_NAME_QUERY == $query->getName()) {
148 1
            return [$this->getAlias($query) => $type->getName()];
149
        }
150
151 65
        $this->resolveValidator->assetTypeHasField($type, $query);
152 65
        $value = $this->resolveField($field, $query);
153
154 65
        return [$this->getAlias($query) => $value];
155
    }
156
157 65
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
158
    {
159
        try {
160
            /** @var AbstractObjectType $type */
161 65
            $type        = $field->getType();
162 65
            $nonNullType = $type->getNullableType();
163
164 65
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
165 2
                return $nonNullType->getName();
166
            }
167
168 65
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
169
170 65
            $targetField = $nonNullType->getField($ast->getName());
171
172 65
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
173 64
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
174
175 59
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
176 59
                case TypeMap::KIND_ENUM:
177 58
                case TypeMap::KIND_SCALAR:
178 52
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
179 2
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation());
180
                    }
181
182 52
                    return $this->resolveScalar($targetField, $ast, $parentValue);
183
184 42 View Code Duplication
                case TypeMap::KIND_OBJECT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
339 2
                        $interfaces = $type->getInterfaces();
340 2
                        foreach ($interfaces as $interface) {
341 1
                            if ($interface->getName() === $astName) {
342
                                $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...
343
344 1
                                break;
345
                            }
346
                        }
347
348 2
                        continue;
349
                    }
350
351 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...
352
353 2
                    break;
354
355 37
                case $astField instanceof FragmentReference:
356 6
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
357 6
                    $astFragmentModel = $astFragment->getModel();
358 6
                    $typeName         = $type->getName();
359
360 6 View Code Duplication
                    if ($typeName !== $astFragmentModel) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
361 1
                        $interfaces = $type->getInterfaces();
362 1
                        foreach ($interfaces as $interface) {
363 1
                            if ($interface->getName() === $astFragmentModel) {
364 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...
365
366 1
                                break;
367
                            }
368
369
                        }
370
371 1
                        continue;
372
                    }
373
374 6
                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
0 ignored issues
show
Documentation introduced by
$astFragment is of type object<Youshido\GraphQL\Parser\Ast\Fragment>|null, but the function expects a object<Youshido\GraphQL\...erfaces\FieldInterface>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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