Completed
Pull Request — master (#32)
by Sebastian
03:51
created

Processor::getPreResolvedValue()   C

Complexity

Conditions 11
Paths 25

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 11.0099

Importance

Changes 6
Bugs 2 Features 1
Metric Value
c 6
b 2
f 1
dl 0
loc 36
ccs 22
cts 23
cp 0.9565
rs 5.2653
cc 11
eloc 21
nc 25
nop 3
crap 11.0099

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\Scalar\AbstractScalarType;
29
use Youshido\GraphQL\Type\TypeInterface;
30
use Youshido\GraphQL\Type\TypeMap;
31
use Youshido\GraphQL\Type\TypeService;
32
use Youshido\GraphQL\Validator\Exception\ResolveException;
33
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
34
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
35
use Youshido\GraphQL\Validator\SchemaValidator\SchemaValidator;
36
37
class Processor
38
{
39
40
    const TYPE_NAME_QUERY = '__typename';
41
42
    /** @var  array */
43
    protected $data;
44
45
    /** @var ResolveValidatorInterface */
46
    protected $resolveValidator;
47
48
    /** @var ExecutionContext */
49
    protected $executionContext;
50
51 23
    public function __construct(AbstractSchema $schema)
52
    {
53 23
        (new SchemaValidator())->validate($schema);
54
55 22
        $this->introduceIntrospectionFields($schema);
56 22
        $this->executionContext = new ExecutionContext();
57 22
        $this->executionContext->setSchema($schema);
58
59 22
        $this->resolveValidator = new ResolveValidator($this->executionContext);
60 22
    }
61
62
63 22
    public function processPayload($payload, $variables = [])
64
    {
65 22
        if ($this->executionContext->hasErrors()) {
66 4
            $this->executionContext->clearErrors();
67 4
        }
68
69 22
        $this->data = [];
70
71
        try {
72 22
            $this->parseAndCreateRequest($payload, $variables);
73
74 22
            $queryType    = $this->executionContext->getSchema()->getQueryType();
75 22
            $mutationType = $this->executionContext->getSchema()->getMutationType();
76 22
            foreach ($this->executionContext->getRequest()->getOperationsInOrder() as $operation) {
77 22
                if ($operationResult = $this->executeOperation($operation, $operation instanceof Mutation ? $mutationType : $queryType)) {
78 20
                    $this->data = array_merge($this->data, $operationResult);
79 20
                };
80 22
            }
81
82 22
        } catch (\Exception $e) {
83 3
            $this->executionContext->addError($e);
84
        }
85
86 22
        return $this;
87
    }
88
89 22
    protected function parseAndCreateRequest($payload, $variables = [])
90
    {
91 22
        if (empty($payload)) {
92 1
            throw new \Exception('Must provide an operation.');
93
        }
94 22
        $parser = new Parser();
95
96 22
        $data = $parser->parse($payload);
97 22
        $this->executionContext->setRequest(new Request($data, $variables));
98 22
    }
99
100
    /**
101
     * @param Query|Field        $query
102
     * @param AbstractObjectType $currentLevelSchema
103
     * @return array|bool|mixed
104
     */
105 22
    protected function executeOperation(Query $query, $currentLevelSchema)
106
    {
107 22
        if (!$this->resolveValidator->objectHasField($currentLevelSchema, $query)) {
108 1
            return null;
109
        }
110
111
        /** @var AbstractField $field */
112 22
        $operationField = $currentLevelSchema->getField($query->getName());
113 22
        $alias          = $query->getAlias() ?: $query->getName();
114
115 22
        if (!$this->resolveValidator->validateArguments($operationField, $query, $this->executionContext->getRequest())) {
116 3
            return null;
117
        }
118
119 20
        return [$alias => $this->processQueryAST($query, $operationField)];
120
    }
121
122
    /**
123
     * @param Query         $query
124
     * @param AbstractField $field
125
     * @param               $contextValue
126
     * @return array|mixed|null
127
     */
128 20
    protected function processQueryAST(Query $query, AbstractField $field, $contextValue = null)
129
    {
130 20
        if (!$this->resolveValidator->validateArguments($field, $query, $this->executionContext->getRequest())) {
131
            return null;
132
        }
133
134 20
        $resolvedValue = $this->resolveFieldValue($field, $contextValue, $query);
135
136 20
        if (!$this->resolveValidator->isValidValueForField($field, $resolvedValue)) {
137 2
            return null;
138
        }
139
140 20
        return $this->collectValueForQueryWithType($query, $field->getType(), $resolvedValue);
141
    }
142
143
    /**
144
     * @param Query|Mutation $query
145
     * @param AbstractType   $fieldType
146
     * @param mixed          $resolvedValue
147
     * @return array|mixed
148
     */
149 20
    protected function collectValueForQueryWithType(Query $query, AbstractType $fieldType, $resolvedValue)
150
    {
151 20
        $fieldType = $this->resolveValidator->resolveTypeIfAbstract($fieldType, $resolvedValue);
152 20
        if (is_null($resolvedValue)) return null;
153
154 18
        $value = [];
155
156 18
        if ($fieldType->getKind() == TypeMap::KIND_LIST) {
157 9
            if (!$this->resolveValidator->hasArrayAccess($resolvedValue)) return null;
158 9
            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...
159 8
                $value[] = [];
160 8
                $index   = count($value) - 1;
161
162
163 8
                $namedType = $fieldType->getNamedType();
164 8
                $namedType = $this->resolveValidator->resolveTypeIfAbstract($namedType, $resolvedValueItem);
165 8
                if (!$namedType->isValidValue($resolvedValueItem)) {
166 1
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid resolve value in %s field', $query->getName())));
167 1
                    $value[$index] = null;
168 1
                    continue;
169
                }
170
171 7
                $value[$index] = $this->processQueryFields($query, $namedType, $resolvedValueItem, $value[$index]);
172 9
            }
173 9
        } else {
174 18
            if (!$query->hasFields()) {
175 2
                return $this->getOutputValue($fieldType, $resolvedValue);
176
            }
177
178 18
            $value = $this->processQueryFields($query, $fieldType, $resolvedValue, $value);
179
        }
180
181 18
        return $value;
182
    }
183
184
    /**
185
     * @param FieldAst      $fieldAst
186
     * @param AbstractField $field
187
     *
188
     * @param mixed         $contextValue
189
     * @return array|mixed|null
190
     * @throws ResolveException
191
     * @throws \Exception
192
     */
193 17
    protected function processFieldAST(FieldAst $fieldAst, AbstractField $field, $contextValue)
194
    {
195 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...
196 17
        $fieldType        = $field->getType();
197 17
        $preResolvedValue = $this->getPreResolvedValue($contextValue, $fieldAst, $field);
198
199 17
        if ($fieldType->getKind() == TypeMap::KIND_LIST) {
200 1
            $listValue = [];
201 1
            foreach ($preResolvedValue as $resolvedValueItem) {
202 1
                $type = $fieldType->getNamedType();
203
204 1
                if (!$type->isValidValue($resolvedValueItem)) {
205
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid resolve value in %s field', $field->getName())));
206
207
                    $listValue = null;
208
                    break;
209
                }
210 1
                $listValue[] = $this->getOutputValue($type, $resolvedValueItem);
211 1
            }
212
213 1
            $value = $listValue;
214 1
        } else {
215 17
            $value = $this->getFieldValidatedValue($field, $preResolvedValue);
216
        }
217
218 17
        return $value;
219
    }
220
221
    /**
222
     * @param AbstractField $field
223
     * @param mixed         $contextValue
224
     * @param Query         $query
225
     *
226
     * @return mixed
227
     */
228 20
    protected function resolveFieldValue(AbstractField $field, $contextValue, Query $query)
229
    {
230 20
        $resolveInfo = new ResolveInfo($field, $query->getFields(), $field->getType(), $this->executionContext);
231
232 20
        if ($resolveFunc = $field->getConfig()->getResolveFunction()) {
233 18
            return $resolveFunc($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
234 9
        } elseif ($propertyValue = TypeService::getPropertyValue($contextValue, $field->getName())) {
235 1
            return $propertyValue;
236
        } else {
237 8
            return $field->resolve($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
238
        }
239
    }
240
241
    /**
242
     * @param               $contextValue
243
     * @param FieldAst      $fieldAst
244
     * @param AbstractField $field
245
     *
246
     * @throws \Exception
247
     *
248
     * @return mixed
249
     */
250 17
    protected function getPreResolvedValue($contextValue, FieldAst $fieldAst, AbstractField $field)
251
    {
252 17
        $resolved      = false;
253 17
        $resolverValue = null;
254
255 17
        if ($resolveFunction = $field->getConfig()->getResolveFunction()) {
256 2
            $resolveInfo = new ResolveInfo($field, [$fieldAst], $field->getType(), $this->executionContext);
257
258 2
            if (!$this->resolveValidator->validateArguments($field, $fieldAst, $this->executionContext->getRequest())) {
259
                throw new \Exception(sprintf('Not valid arguments for the field "%s"', $fieldAst->getName()));
260
261
            } else {
262 2
                $resolverValue = $resolveFunction($resolved ? $resolverValue : $contextValue, $fieldAst->getKeyValueArguments(), $resolveInfo);
263 2
                $resolved      = true;
264
            }
265
266 2
        }
267
268 17
        if (is_array($contextValue) && array_key_exists($fieldAst->getName(), $contextValue)) {
269 13
            $resolverValue = $contextValue[$fieldAst->getName()];
270 13
            $resolved      = true;
271 17
        } elseif (is_object($contextValue)) {
272 6
            $resolverValue = TypeService::getPropertyValue($contextValue, $fieldAst->getName());
273 6
            $resolved      = true;
274 6
        }
275
276 17
        if (!$resolved && $field->getType()->getNamedType()->getKind() == TypeMap::KIND_SCALAR) {
277 1
            $resolved = true;
278 1
        }
279
280 17
        if (!$resolverValue && !$resolved) {
281 1
            throw new \Exception(sprintf('Property "%s" not found in resolve result', $fieldAst->getName()));
282
        }
283
284 17
        return $resolverValue;
285
    }
286
287
    /**
288
     * @param $field     AbstractField
289
     * @param $query     Query
290
     *
291
     * @return array
292
     */
293 20
    protected function parseArgumentsValues(AbstractField $field, Query $query)
294
    {
295 20
        $args = [];
296 20
        foreach ($query->getArguments() as $argument) {
297 8
            if ($configArgument = $field->getConfig()->getArgument($argument->getName())) {
298 8
                $args[$argument->getName()] = $configArgument->getType()->parseValue($argument->getValue()->getValue());
299 8
            }
300 20
        }
301
302 20
        return $args;
303
    }
304
305
    /**
306
     * @param $query         Query|FragmentInterface
307
     * @param $queryType     AbstractObjectType|TypeInterface|Field|AbstractType
308
     * @param $resolvedValue mixed
309
     * @param $value         array
310
     *
311
     * @throws \Exception
312
     *
313
     * @return array
314
     */
315 18
    protected function processQueryFields($query, AbstractType $queryType, $resolvedValue, $value)
316
    {
317 18
        if ($queryType instanceof AbstractScalarType && !$query->hasFields()) {
318 1
            return $this->getOutputValue($queryType, $resolvedValue);
319
        }
320
321 18
        foreach ($query->getFields() as $fieldAst) {
322 18
            $fieldResolvedValue = null;
323
324 18
            if ($fieldAst instanceof FragmentInterface) {
325
                /** @var TypedFragmentReference $fragment */
326 3
                $fragment = $fieldAst;
327 3
                if ($fieldAst instanceof FragmentReference) {
328
                    /** @var Fragment $fragment */
329 2
                    $fragment = $this->executionContext->getRequest()->getFragment($fieldAst->getName());
330 2
                    $this->resolveValidator->assertValidFragmentForField($fragment, $fieldAst, $queryType);
331 3
                } elseif ($fragment->getTypeName() !== $queryType->getName()) {
332 1
                    continue;
333
                }
334
335 3
                $fragmentValue      = $this->processQueryFields($fragment, $queryType, $resolvedValue, $value);
336 3
                $fieldResolvedValue = is_array($fragmentValue) ? $fragmentValue : [];
337 3
            } else {
338 18
                $alias       = $fieldAst->getAlias() ?: $fieldAst->getName();
339 18
                $currentType = $queryType->getNullableType();
340
341 18
                if ($fieldAst->getName() == self::TYPE_NAME_QUERY) {
342 1
                    $fieldResolvedValue = [$alias => $queryType->getName()];
343 1
                } else {
344 18
                    if (!$this->resolveValidator->objectHasField($currentType, $fieldAst)) {
345 2
                        $fieldResolvedValue = null;
346 2
                    } else {
347 18
                        if ($fieldAst instanceof Query) {
348 10
                            $queryAst           = $currentType->getField($fieldAst->getName());
349 10
                            $fieldValue         = $queryAst ? $this->processQueryAST($fieldAst, $queryAst, $resolvedValue) : null;
350 10
                            $fieldResolvedValue = [$alias => $fieldValue];
351 18
                        } elseif ($fieldAst instanceof FieldAst) {
352
                            $fieldResolvedValue = [
353 17
                                $alias => $this->processFieldAST($fieldAst, $currentType->getField($fieldAst->getName()), $resolvedValue)
354 17
                            ];
355 17
                        }
356
                    }
357
358
359
                }
360
            }
361
362 18
            $value = $this->collectValue($value, $fieldResolvedValue);
363 18
        }
364
365 18
        return $value;
366
    }
367
368 17
    protected function getFieldValidatedValue(AbstractField $field, $value)
369
    {
370 17
        return ($this->resolveValidator->isValidValueForField($field, $value)) ? $this->getOutputValue($field->getType(), $value) : null;
371
    }
372
373 17
    protected function getOutputValue(AbstractType $type, $value)
374
    {
375 17
        return in_array($type->getKind(), [TypeMap::KIND_OBJECT, TypeMap::KIND_NON_NULL]) ? $value : $type->serialize($value);
376
    }
377
378 18
    protected function collectValue($value, $queryValue)
379
    {
380 18
        if ($queryValue && is_array($queryValue)) {
381 18
            $value = array_merge(is_array($value) ? $value : [], $queryValue);
382 18
        } else {
383 2
            $value = $queryValue;
384
        }
385
386 18
        return $value;
387
    }
388
389 22
    protected function introduceIntrospectionFields(AbstractSchema $schema)
390
    {
391 22
        $schemaField = new SchemaField();
392 22
        $schemaField->setSchema($schema);
393
394 22
        $schema->addQueryField($schemaField);
395 22
        $schema->addQueryField(new TypeDefinitionField());
396 22
    }
397
398 22
    public function getResponseData()
399
    {
400 22
        $result = [];
401
402 22
        if (!empty($this->data)) {
403 20
            $result['data'] = $this->data;
404 20
        }
405
406 22
        if ($this->executionContext->hasErrors()) {
407 6
            $result['errors'] = $this->executionContext->getErrorsArray();
408 6
        }
409
410 22
        return $result;
411
    }
412
}
413