Completed
Push — master ( 8c19ea...8f4b0a )
by Portey
03:08
created

Processor::walkQuery()   C

Complexity

Conditions 15
Paths 4

Size

Total Lines 53
Code Lines 33

Duplication

Lines 18
Ratio 33.96 %

Code Coverage

Tests 42
CRAP Score 15.0211

Importance

Changes 0
Metric Value
dl 18
loc 53
rs 6.2939
c 0
b 0
f 0
ccs 42
cts 44
cp 0.9545
cc 15
eloc 33
nc 4
nop 2
crap 15.0211

2 Methods

Rating   Name   Duplication   Size   Complexity  
A Processor::getAlias() 0 4 2
A Processor::createResolveInfo() 0 4 1

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