Completed
Push — master ( f1bb68...f91685 )
by Alexandr
03:16
created

Processor::processQueryFields()   C

Complexity

Conditions 12
Paths 18

Size

Total Lines 48
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 12

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 48
rs 5.1266
ccs 32
cts 32
cp 1
cc 12
eloc 30
nc 18
nop 4
crap 12

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
* This file is a part of graphql-youshido project.
4
*
5
* @author Portey Vasil <[email protected]>
6
* @author Alexandr Viniychuk <[email protected]>
7
* created: 11/28/15 1:05 AM
8
*/
9
10
namespace Youshido\GraphQL\Execution;
11
12
use Youshido\GraphQL\Execution\Context\ExecutionContext;
13
use Youshido\GraphQL\Field\AbstractField;
14
use Youshido\GraphQL\Field\Field;
15
use Youshido\GraphQL\Introspection\Field\SchemaField;
16
use Youshido\GraphQL\Introspection\Field\TypeDefinitionField;
17
use Youshido\GraphQL\Parser\Ast\Field as FieldAst;
18
use Youshido\GraphQL\Parser\Ast\Fragment;
19
use Youshido\GraphQL\Parser\Ast\FragmentInterface;
20
use Youshido\GraphQL\Parser\Ast\FragmentReference;
21
use Youshido\GraphQL\Parser\Ast\Mutation;
22
use Youshido\GraphQL\Parser\Ast\Query;
23
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
24
use Youshido\GraphQL\Parser\Parser;
25
use Youshido\GraphQL\Schema\AbstractSchema;
26
use Youshido\GraphQL\Type\AbstractType;
27
use Youshido\GraphQL\Type\Object\AbstractObjectType;
28
use Youshido\GraphQL\Type\TypeInterface;
29
use Youshido\GraphQL\Type\TypeMap;
30
use Youshido\GraphQL\Type\TypeService;
31
use Youshido\GraphQL\Validator\Exception\ResolveException;
32
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
33
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
34
use Youshido\GraphQL\Validator\SchemaValidator\SchemaValidator;
35
36
class Processor
37
{
38
39
    const TYPE_NAME_QUERY = '__typename';
40
41
    /** @var  array */
42
    protected $data;
43
44
    /** @var ResolveValidatorInterface */
45
    protected $resolveValidator;
46
47
    /** @var ExecutionContext */
48
    protected $executionContext;
49
50 23
    public function __construct(AbstractSchema $schema)
51
    {
52 23
        (new SchemaValidator())->validate($schema);
53
54 22
        $this->introduceIntrospectionFields($schema);
55 22
        $this->executionContext = new ExecutionContext();
56 22
        $this->executionContext->setSchema($schema);
57
58 22
        $this->resolveValidator = new ResolveValidator($this->executionContext);
59 22
    }
60
61
62 22
    public function processPayload($payload, $variables = [])
63
    {
64 22
        if ($this->executionContext->hasErrors()) {
65 4
            $this->executionContext->clearErrors();
66 4
        }
67
68 22
        $this->data = [];
69
70
        try {
71 22
            $this->parseAndCreateRequest($payload, $variables);
72
73 22
            $queryType    = $this->executionContext->getSchema()->getQueryType();
74 22
            $mutationType = $this->executionContext->getSchema()->getMutationType();
75 22
            foreach ($this->executionContext->getRequest()->getOperationsInOrder() as $operation) {
76 22
                if ($operationResult = $this->executeOperation($operation, $operation instanceof Mutation ? $mutationType : $queryType)) {
77 20
                    $this->data = array_merge($this->data, $operationResult);
78 20
                };
79 22
            }
80
81 22
        } catch (\Exception $e) {
82 3
            $this->executionContext->addError($e);
83
        }
84
85 22
        return $this;
86
    }
87
88 22
    protected function parseAndCreateRequest($payload, $variables = [])
89
    {
90 22
        if (empty($payload)) {
91 1
            throw new \Exception('Must provide an operation.');
92
        }
93 22
        $parser = new Parser();
94
95 22
        $data = $parser->parse($payload);
96 22
        $this->executionContext->setRequest(new Request($data, $variables));
97 22
    }
98
99
    /**
100
     * @param Query|Field        $query
101
     * @param AbstractObjectType $currentLevelSchema
102
     * @return array|bool|mixed
103
     */
104 22
    protected function executeOperation(Query $query, $currentLevelSchema)
105
    {
106 22
        if (!$this->resolveValidator->objectHasField($currentLevelSchema, $query)) {
107 1
            return null;
108
        }
109
110
        /** @var AbstractField $field */
111 22
        $operationField = $currentLevelSchema->getField($query->getName());
112 22
        $alias          = $query->getAlias() ?: $query->getName();
113
114 22
        if (!$this->resolveValidator->validateArguments($operationField, $query, $this->executionContext->getRequest())) {
115 3
            return null;
116
        }
117
118 20
        return [$alias => $this->processQueryAST($query, $operationField)];
119
    }
120
121
    /**
122
     * @param Query         $query
123
     * @param AbstractField $field
124
     * @param               $contextValue
125
     * @return array|mixed|null
126
     */
127 20
    protected function processQueryAST(Query $query, AbstractField $field, $contextValue = null)
128
    {
129 20
        if (!$this->resolveValidator->validateArguments($field, $query, $this->executionContext->getRequest())) {
130
            return null;
131
        }
132
133 20
        $resolvedValue = $this->resolveFieldValue($field, $contextValue, $query);
134
135 20
        if (!$this->resolveValidator->isValidValueForField($field, $resolvedValue)) {
136 2
            return null;
137
        }
138
139 20
        return $this->collectValueForQueryWithType($query, $field->getType(), $resolvedValue);
140
    }
141
142
    /**
143
     * @param Query|Mutation $query
144
     * @param AbstractType   $fieldType
145
     * @param mixed          $resolvedValue
146
     * @return array|mixed
147
     */
148 20
    protected function collectValueForQueryWithType(Query $query, AbstractType $fieldType, $resolvedValue)
149
    {
150 20
        $fieldType = $this->resolveValidator->resolveTypeIfAbstract($fieldType, $resolvedValue);
151 20
        if (is_null($resolvedValue)) return null;
152
153 18
        $value = [];
154 18
        if ($fieldType->getKind() == TypeMap::KIND_LIST) {
155 8
            if (!$this->resolveValidator->hasArrayAccess($resolvedValue)) return null;
156 8
            foreach ($resolvedValue as $resolvedValueItem) {
0 ignored issues
show
Bug introduced by
The expression $resolvedValue of type object|integer|double|string|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
157 7
                $value[] = [];
158 7
                $index   = count($value) - 1;
159
160
161 7
                $namedType = $fieldType->getNamedType();
162 7
                $namedType = $this->resolveValidator->resolveTypeIfAbstract($namedType, $resolvedValueItem);
163 7
                if (!$namedType->isValidValue($resolvedValueItem)) {
164 1
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid resolve value in %s field', $query->getName())));
165 1
                    $value[$index] = null;
166 1
                    continue;
167
                }
168
169 6
                $value[$index] = $this->processQueryFields($query, $namedType, $resolvedValueItem, $value[$index]);
170 8
            }
171 8
        } else {
172 18
            if (!$query->hasFields()) {
173 2
                return $this->getOutputValue($fieldType, $resolvedValue);
174
            }
175
176 18
            $value = $this->processQueryFields($query, $fieldType, $resolvedValue, $value);
177
        }
178
179 18
        return $value;
180
    }
181
182
    /**
183
     * @param FieldAst      $fieldAst
184
     * @param AbstractField $field
185
     *
186
     * @param mixed         $contextValue
187
     * @return array|mixed|null
188
     * @throws ResolveException
189
     * @throws \Exception
190
     */
191 17
    protected function processFieldAST(FieldAst $fieldAst, AbstractField $field, $contextValue)
192
    {
193 17
        $value            = null;
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
194 17
        $fieldType        = $field->getType();
195 17
        $preResolvedValue = $this->getPreResolvedValue($contextValue, $fieldAst, $field);
196
197 17
        if ($fieldType->getKind() == TypeMap::KIND_LIST) {
198 1
            $listValue = [];
199 1
            foreach ($preResolvedValue as $resolvedValueItem) {
200 1
                $type = $fieldType->getNamedType();
201
202 1
                if (!$type->isValidValue($resolvedValueItem)) {
203
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid resolve value in %s field', $field->getName())));
204
205
                    $listValue = null;
206
                    break;
207
                }
208 1
                $listValue[] = $this->getOutputValue($type, $resolvedValueItem);
209 1
            }
210
211 1
            $value = $listValue;
212 1
        } else {
213 17
            $value = $this->getFieldValidatedValue($field, $preResolvedValue);
214
        }
215
216 17
        return $value;
217
    }
218
219
    /**
220
     * @param AbstractField $field
221
     * @param mixed         $contextValue
222
     * @param Query         $query
223
     *
224
     * @return mixed
225
     */
226 20
    protected function resolveFieldValue(AbstractField $field, $contextValue, Query $query)
227
    {
228 20
        $resolveInfo = new ResolveInfo($field, $query->getFields(), $field->getType(), $this->executionContext);
229
230 20
        if ($resolveFunc = $field->getConfig()->getResolveFunction()) {
231 18
            return $resolveFunc($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
232 9
        } elseif ($propertyValue = TypeService::getPropertyValue($contextValue, $field->getName())) {
233 1
            return $propertyValue;
234
        } else {
235 8
            return $field->resolve($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
236
        }
237
    }
238
239
    /**
240
     * @param               $contextValue
241
     * @param FieldAst      $fieldAst
242
     * @param AbstractField $field
243
     *
244
     * @throws \Exception
245
     *
246
     * @return mixed
247
     */
248 17
    protected function getPreResolvedValue($contextValue, FieldAst $fieldAst, AbstractField $field)
249
    {
250 17
        $resolved      = false;
251 17
        $resolverValue = null;
252
253 17
        if (is_array($contextValue) && array_key_exists($fieldAst->getName(), $contextValue)) {
254 13
            $resolverValue = $contextValue[$fieldAst->getName()];
255 13
            $resolved      = true;
256 17
        } elseif (is_object($contextValue)) {
257 6
            $resolverValue = TypeService::getPropertyValue($contextValue, $fieldAst->getName());
258 6
            $resolved      = true;
259 6
        }
260
261 17
        if (!$resolved && $field->getType()->getNamedType()->getKind() == TypeMap::KIND_SCALAR) {
262 2
            $resolved = true;
263 2
        }
264
265 17
        if ($resolveFunction = $field->getConfig()->getResolveFunction()) {
266 2
            $resolveInfo = new ResolveInfo($field, [$fieldAst], $field->getType(), $this->executionContext);
267
268 2
            if (!$this->resolveValidator->validateArguments($field, $fieldAst, $this->executionContext->getRequest())) {
269
                throw new \Exception(sprintf('Not valid arguments for the field "%s"', $fieldAst->getName()));
270
271
            } else {
272 2
                $resolverValue = $resolveFunction($resolved ? $resolverValue : $contextValue, $fieldAst->getKeyValueArguments(), $resolveInfo);
273
            }
274
275 2
        }
276
277 17
        if (!$resolverValue && !$resolved) {
278 1
            throw new \Exception(sprintf('Property "%s" not found in resolve result', $fieldAst->getName()));
279
        }
280
281 17
        return $resolverValue;
282
    }
283
284
    /**
285
     * @param $field     AbstractField
286
     * @param $query     Query
287
     *
288
     * @return array
289
     */
290 20
    protected function parseArgumentsValues(AbstractField $field, Query $query)
291
    {
292 20
        $args = [];
293 20
        foreach ($query->getArguments() as $argument) {
294 8
            if ($configArgument = $field->getConfig()->getArgument($argument->getName())) {
295 8
                $args[$argument->getName()] = $configArgument->getType()->parseValue($argument->getValue()->getValue());
296 8
            }
297 20
        }
298
299 20
        return $args;
300
    }
301
302
    /**
303
     * @param $query         Query|FragmentInterface
304
     * @param $queryType     AbstractObjectType|TypeInterface|Field|AbstractType
305
     * @param $resolvedValue mixed
306
     * @param $value         array
307
     *
308
     * @throws \Exception
309
     *
310
     * @return array
311
     */
312 18
    protected function processQueryFields($query, AbstractType $queryType, $resolvedValue, $value)
313
    {
314 18
        foreach ($query->getFields() as $fieldAst) {
315 18
            $fieldResolvedValue = null;
316
317 18
            if ($fieldAst instanceof FragmentInterface) {
318
                /** @var TypedFragmentReference $fragment */
319 3
                $fragment = $fieldAst;
320 3
                if ($fieldAst instanceof FragmentReference) {
321
                    /** @var Fragment $fragment */
322 2
                    $fragment = $this->executionContext->getRequest()->getFragment($fieldAst->getName());
323 2
                    $this->resolveValidator->assertValidFragmentForField($fragment, $fieldAst, $queryType);
324 3
                } elseif ($fragment->getTypeName() !== $queryType->getName()) {
325 1
                    continue;
326
                }
327
328 3
                $fragmentValue      = $this->processQueryFields($fragment, $queryType, $resolvedValue, $value);
329 3
                $fieldResolvedValue = is_array($fragmentValue) ? $fragmentValue : [];
330 3
            } else {
331 18
                $alias       = $fieldAst->getAlias() ?: $fieldAst->getName();
332 18
                $currentType = $queryType->getNullableType();
333
334 18
                if ($fieldAst->getName() == self::TYPE_NAME_QUERY) {
335 1
                    $fieldResolvedValue = [$alias => $queryType->getName()];
336 1
                } else {
337 18
                    if (!$this->resolveValidator->objectHasField($currentType, $fieldAst)) {
338 2
                        $fieldResolvedValue = null;
339 2
                    } else {
340 18
                        if ($fieldAst instanceof Query) {
341 10
                            $queryAst           = $currentType->getField($fieldAst->getName());
342 10
                            $fieldValue         = $queryAst ? $this->processQueryAST($fieldAst, $queryAst, $resolvedValue) : null;
343 10
                            $fieldResolvedValue = [$alias => $fieldValue];
344 18
                        } elseif ($fieldAst instanceof FieldAst) {
345
                            $fieldResolvedValue = [
346 17
                                $alias => $this->processFieldAST($fieldAst, $currentType->getField($fieldAst->getName()), $resolvedValue)
347 17
                            ];
348 17
                        }
349
                    }
350
351
352
                }
353
            }
354
355 18
            $value = $this->collectValue($value, $fieldResolvedValue);
356 18
        }
357
358 18
        return $value;
359
    }
360
361 17
    protected function getFieldValidatedValue(AbstractField $field, $value)
362
    {
363 17
        return ($this->resolveValidator->isValidValueForField($field, $value)) ? $this->getOutputValue($field->getType(), $value) : null;
364
    }
365
366 17
    protected function getOutputValue(AbstractType $type, $value)
367
    {
368 17
        return in_array($type->getKind(), [TypeMap::KIND_OBJECT, TypeMap::KIND_NON_NULL]) ? $value : $type->serialize($value);
369
    }
370
371 18
    protected function collectValue($value, $queryValue)
372
    {
373 18
        if ($queryValue && is_array($queryValue)) {
374 18
            $value = array_merge(is_array($value) ? $value : [], $queryValue);
375 18
        } else {
376 2
            $value = $queryValue;
377
        }
378
379 18
        return $value;
380
    }
381
382 22
    protected function introduceIntrospectionFields(AbstractSchema $schema)
383
    {
384 22
        $schemaField = new SchemaField();
385 22
        $schemaField->setSchema($schema);
386
387 22
        $schema->addQueryField($schemaField);
388 22
        $schema->addQueryField(new TypeDefinitionField());
389 22
    }
390
391 22
    public function getResponseData()
392
    {
393 22
        $result = [];
394
395 22
        if (!empty($this->data)) {
396 20
            $result['data'] = $this->data;
397 20
        }
398
399 22
        if ($this->executionContext->hasErrors()) {
400 6
            $result['errors'] = $this->executionContext->getErrorsArray();
401 6
        }
402
403 22
        return $result;
404
    }
405
}
406