Completed
Push — master ( 1049ef...ba8cbe )
by Portey
7s
created

Processor::processQueryFields()   C

Complexity

Conditions 11
Paths 16

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 11.0619

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 44
ccs 23
cts 25
cp 0.92
rs 5.2653
cc 11
eloc 28
nc 16
nop 4
crap 11.0619

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