Completed
Push — master ( d63187...118ce9 )
by Portey
03:37
created

Processor::prepareArgumentValue()   D

Complexity

Conditions 16
Paths 14

Size

Total Lines 48
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 17.3116

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 48
ccs 24
cts 29
cp 0.8276
rs 4.9765
cc 16
eloc 31
nc 14
nop 3
crap 17.3116

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Execution\Container\Container;
12
use Youshido\GraphQL\Execution\Context\ExecutionContext;
13
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
14
use Youshido\GraphQL\Field\Field;
15
use Youshido\GraphQL\Field\FieldInterface;
16
use Youshido\GraphQL\Field\InputField;
17
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList as AstInputList;
18
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject as AstInputObject;
19
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal as AstLiteral;
20
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
21
use Youshido\GraphQL\Parser\Ast\Field as AstField;
22
use Youshido\GraphQL\Parser\Ast\FragmentReference;
23
use Youshido\GraphQL\Parser\Ast\Interfaces\FieldInterface as AstFieldInterface;
24
use Youshido\GraphQL\Parser\Ast\Mutation as AstMutation;
25
use Youshido\GraphQL\Parser\Ast\Query as AstQuery;
26
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
27
use Youshido\GraphQL\Parser\Parser;
28
use Youshido\GraphQL\Schema\AbstractSchema;
29
use Youshido\GraphQL\Type\AbstractType;
30
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
31
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
32
use Youshido\GraphQL\Type\ListType\AbstractListType;
33
use Youshido\GraphQL\Type\Object\AbstractObjectType;
34
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
35
use Youshido\GraphQL\Type\TypeMap;
36
use Youshido\GraphQL\Type\Union\AbstractUnionType;
37
use Youshido\GraphQL\Validator\Exception\ResolveException;
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 39
    public function __construct(AbstractSchema $schema)
60
    {
61 39
        if (empty($this->executionContext)) {
62 39
            $this->executionContext = new ExecutionContext($schema);
63 39
            $this->executionContext->setContainer(new Container());
64
        }
65
66 39
        $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...
67 39
    }
68
69 37
    public function processPayload($payload, $variables = [], $reducers = [])
70
    {
71 37
        $this->data = [];
72
73
        try {
74 37
            $this->parseAndCreateRequest($payload, $variables);
75
76 36
            if ($this->maxComplexity) {
77 1
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
78
            }
79
80 36
            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...
81 2
                $reducer = new Reducer();
82 2
                $reducer->reduceQuery($this->executionContext, $reducers);
83
            }
84
85 36
            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
86 36
                if ($operationResult = $this->resolveQuery($query)) {
87 36
                    $this->data = array_merge($this->data, $operationResult);
88
                };
89
            }
90 5
        } catch (\Exception $e) {
91 5
            $this->executionContext->addError($e);
92
        }
93
94 37
        return $this;
95
    }
96
97 37
    public function getResponseData()
98
    {
99 37
        $result = [];
100
101 37
        if (!empty($this->data)) {
102 36
            $result['data'] = $this->data;
103
        }
104
105 37
        if ($this->executionContext->hasErrors()) {
106 15
            $result['errors'] = $this->executionContext->getErrorsArray();
107
        }
108
109 37
        return $result;
110
    }
111
112
    /**
113
     * You can access ExecutionContext to check errors and inject dependencies
114
     *
115
     * @return ExecutionContext
116
     */
117 10
    public function getExecutionContext()
118
    {
119 10
        return $this->executionContext;
120
    }
121
122
    /**
123
     * @return int
124
     */
125
    public function getMaxComplexity()
126
    {
127
        return $this->maxComplexity;
128
    }
129
130
    /**
131
     * @param int $maxComplexity
132
     */
133 1
    public function setMaxComplexity($maxComplexity)
134
    {
135 1
        $this->maxComplexity = $maxComplexity;
136 1
    }
137
138 36
    protected function resolveQuery(AstQuery $query)
139
    {
140 36
        $schema = $this->executionContext->getSchema();
141 36
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
142 36
        $field  = new Field([
143 36
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
144 36
            'type' => $type
145
        ]);
146
147 36
        $this->resolveValidator->assetTypeHasField($type, $query);
148 36
        $value = $this->resolveField($field, $query);
149
150 36
        return [$this->getAlias($query) => $value];
151
    }
152
153 36
    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
154
    {
155
        try {
156
            /** @var AbstractObjectType $type */
157 36
            $type        = $field->getType();
158 36
            $nonNullType = $type->getNullableType();
159
160 36
            if (self::TYPE_NAME_QUERY == $ast->getName()) {
161 2
                return $nonNullType->getName();
162
            }
163
164 36
            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);
165
166 36
            $targetField = $nonNullType->getField($ast->getName());
167
168 36
            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
169 35
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());
170
171 33
            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
172 33
                case TypeMap::KIND_ENUM:
173 33
                case TypeMap::KIND_SCALAR:
174 27
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
175 2
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()));
176
                    }
177
178 27
                    return $this->resolveScalar($targetField, $ast, $parentValue);
179
180 28 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...
181
                    /** @var $type AbstractObjectType */
182 20
                    if (!$ast instanceof AstQuery) {
183 1
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()));
184
                    }
185
186 20
                    return $this->resolveObject($targetField, $ast, $parentValue);
187
188 17
                case TypeMap::KIND_LIST:
189 14
                    return $this->resolveList($targetField, $ast, $parentValue);
190
191 6
                case TypeMap::KIND_UNION:
192 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...
193 6
                    if (!$ast instanceof AstQuery) {
194
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()));
195
                    }
196
197 6
                    return $this->resolveComposite($targetField, $ast, $parentValue);
198
199
                default:
200
                    throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind));
201
            }
202 12
        } catch (\Exception $e) {
203 12
            $this->executionContext->addError($e);
204
205 12
            if ($fromObject) {
206 4
                throw $e;
207
            }
208
209 10
            return null;
210
        }
211
    }
212
213 36
    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
214
    {
215 36
        foreach ($query->getArguments() as $astArgument) {
216 15
            if ($field->hasArgument($astArgument->getName())) {
217 15
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();
218
219 15
                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
220
            }
221
        }
222 35
    }
223
224 15
    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
225
    {
226 15
        switch ($argumentType->getKind()) {
227 15
            case TypeMap::KIND_LIST:
228
                /** @var $argumentType AbstractListType */
229 2
                $result = [];
230 2
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
231 2
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
232 2
                    foreach ($list as $item) {
233 2
                        $result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request);
234
                    }
235
                } else if ($argumentValue instanceof VariableReference) {
236
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
237
                }
238
239 2
                return $result;
240
241 15
            case TypeMap::KIND_INPUT_OBJECT:
242
                /** @var $argumentType AbstractInputObjectType */
243 2
                $result = [];
244 2
                if ($argumentValue instanceof AstInputObject) {
245 2
                    foreach ($argumentValue->getValue() as $key => $item) {
246 2
                        if ($argumentType->hasField($key)) {
247 2
                            $result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request);
248
                        } else {
249 2
                            $result[$key] = $item;
250
                        }
251
                    }
252
                } else if ($argumentValue instanceof VariableReference) {
253
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
254
                }
255
256 2
                return $result;
257
258 15
            case TypeMap::KIND_SCALAR:
259 2
            case TypeMap::KIND_ENUM:
260
                /** @var $argumentValue AstLiteral|VariableReference */
261 15
                if ($argumentValue instanceof VariableReference) {
262 4
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
263 12
                } else if ($argumentValue instanceof AstLiteral) {
264 10
                    return $argumentValue->getValue();
265
                } else {
266 3
                    return $argumentValue;
267
                }
268
        }
269
270
        throw new ResolveException('Argument type not supported');
271
    }
272
273 4
    private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request)
274
    {
275 4
        $variable = $variableReference->getVariable();
276 4
        if ($argumentType->getKind() == TypeMap::KIND_LIST) {
277 View Code Duplication
            if ($variable->getTypeName() != $argumentType->getNamedType()->getName()) {
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...
278
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()));
279
            }
280 View Code Duplication
        } else {
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...
281 4
            if ($variable->getTypeName() != $argumentType->getName()) {
282 1
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()));
283
            }
284
        }
285
286 3
        $requestValue = $request->getVariable($variable->getName());
287 3
        if (!$request->hasVariable($variable->getName()) || (null === $requestValue && $variable->isNullable())) {
288
            throw  new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()));
289
        }
290
291 3
        return $requestValue;
292
    }
293
294 25
    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
295
    {
296 25
        if (!$fromUnion) {
297 20
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
298
        } else {
299 7
            $resolvedValue = $parentValue;
300
        }
301
302 25
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
303
304 25
        if (null === $resolvedValue) {
305 5
            return null;
306
        }
307
        /** @var AbstractObjectType $type */
308 24
        $type = $field->getType()->getNullableType();
309
310
        try {
311 24
            return $this->collectResult($field, $type, $ast, $resolvedValue);
312 4
        } catch (\Exception $e) {
313 4
            return null;
314
        }
315
    }
316
317 24
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
318
    {
319
        /** @var AstQuery $ast */
320 24
        $result = [];
321
322 24
        foreach ($ast->getFields() as $astField) {
323
            switch (true) {
324 24
                case $astField instanceof TypedFragmentReference:
325 2
                    $astName  = $astField->getTypeName();
326 2
                    $typeName = $type->getName();
327
328 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...
329 2
                        foreach ($type->getInterfaces() as $interface) {
330 1
                            if ($interface->getName() === $astName) {
331
                                $result = array_merge($result, $this->collectResult($field, $type, $astField, $resolvedValue));
332
333 1
                                break;
334
                            }
335
                        }
336
337 2
                        continue;
338
                    }
339
340 2
                    $result = array_merge($result, $this->collectResult($field, $type, $astField, $resolvedValue));
341
342 2
                    break;
343
344 24
                case $astField instanceof FragmentReference:
345 3
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
346 3
                    $astFragmentModel = $astFragment->getModel();
347 3
                    $typeName         = $type->getName();
348
349 3 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...
350 1
                        foreach ($type->getInterfaces() as $interface) {
351 1
                            if ($interface->getName() === $astFragmentModel) {
352 1
                                $result = array_merge($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
353
354 1
                                break;
355
                            }
356
357
                        }
358
359 1
                        continue;
360
                    }
361
362 3
                    $result = array_merge($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));
363
364 3
                    break;
365
366
                default:
367 24
                    $result[$this->getAlias($astField)] = $this->resolveField($field, $astField, $resolvedValue, true);
368
            }
369
        }
370
371 24
        return $result;
372
    }
373
374 28
    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
375
    {
376 28
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
377
378 28
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
379
380
        /** @var AbstractScalarType $type */
381 27
        $type = $field->getType()->getNullableType();
382
383 27
        return $type->serialize($resolvedValue);
384
    }
385
386 14
    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
387
    {
388
        /** @var AstQuery $ast */
389 14
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
390
391 14
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
392
393 13
        if (null === $resolvedValue) {
394 5
            return null;
395
        }
396
397
        /** @var AbstractListType $type */
398 12
        $type     = $field->getType()->getNullableType();
399 12
        $itemType = $type->getNamedType();
400
401 12
        $fakeAst = clone $ast;
402 12
        if ($fakeAst instanceof AstQuery) {
403 12
            $fakeAst->setArguments([]);
404
        }
405
406 12
        $fakeField = new Field([
407 12
            'name' => $field->getName(),
408 12
            'type' => $itemType,
409
        ]);
410
411 12
        $result = [];
412 12
        foreach ($resolvedValue as $resolvedValueItem) {
413
            try {
414 11
                $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
415 11
                    return $resolvedValueItem;
416 11
                });
417
418 11
                switch ($itemType->getNullableType()->getKind()) {
419 11
                    case TypeMap::KIND_ENUM:
420 10
                    case TypeMap::KIND_SCALAR:
421 4
                        $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);
422
423 3
                        break;
424
425
426 8
                    case TypeMap::KIND_OBJECT:
427 6
                        $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);
428
429 6
                        break;
430
431 3
                    case TypeMap::KIND_UNION:
432 3
                    case TypeMap::KIND_INTERFACE:
433 3
                        $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);
434
435 3
                        break;
436
437
                    default:
438 10
                        $value = null;
439
                }
440 2
            } catch (\Exception $e) {
441 2
                $this->executionContext->addError($e);
442
443 2
                $value = null;
444
            }
445
446 11
            $result[] = $value;
447
        }
448
449 12
        return $result;
450
    }
451
452 7
    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
453
    {
454
        /** @var AstQuery $ast */
455 7
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
456
457 7
        $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);
458
459
        /** @var AbstractUnionType $type */
460 7
        $type         = $field->getType()->getNullableType();
461 7
        $resolvedType = $type->resolveType($resolvedValue);
462
463 7
        if (!$resolvedType) {
464
            throw new ResolveException('Resoling function must return type');
465
        }
466
467 7
        if ($type instanceof AbstractInterfaceType) {
468 6
            $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
469
        } else {
470 1
            $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
471
        }
472
473 7
        $fakeField = new Field([
474 7
            'name' => $field->getName(),
475 7
            'type' => $resolvedType,
476
        ]);
477
478 7
        return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
479
    }
480
481 37
    protected function parseAndCreateRequest($payload, $variables = [])
482
    {
483 37
        if (empty($payload)) {
484 1
            throw new \InvalidArgumentException('Must provide an operation.');
485
        }
486
487 37
        $parser  = new Parser();
488 37
        $request = new Request($parser->parse($payload), $variables);
489
490 37
        (new RequestValidator())->validate($request);
491
492 36
        $this->executionContext->setRequest($request);
493 36
    }
494
495 33
    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
496
    {
497
        /** @var AstQuery|AstField $ast */
498 33
        $arguments = $this->parseArgumentsValues($field, $ast);
499 33
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];
500
501 33
        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
502
    }
503
504 33
    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
505
    {
506 33
        $values   = [];
507 33
        $defaults = [];
508
509 33
        foreach ($field->getArguments() as $argument) {
510
            /** @var $argument InputField */
511 19
            if ($argument->getConfig()->has('default')) {
512 19
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
513
            }
514
        }
515
516 33
        foreach ($ast->getArguments() as $astArgument) {
517 13
            $argument     = $field->getArgument($astArgument->getName());
518 13
            $argumentType = $argument->getType()->getNullableType();
519
520 13
            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());
521
522 13
            if (isset($defaults[$argument->getName()])) {
523 13
                unset($defaults[$argument->getName()]);
524
            }
525
        }
526
527 33
        return array_merge($values, $defaults);
528
    }
529
530 36
    private function getAlias(AstFieldInterface $ast)
531
    {
532 36
        return $ast->getAlias() ?: $ast->getName();
533
    }
534
535 33
    protected function createResolveInfo(FieldInterface $field, array $astFields)
536
    {
537 33
        return new ResolveInfo($field, $astFields, $this->executionContext);
538
    }
539
540
}
541