Completed
Push — master ( d52ee7...e7842c )
by Alexandr
03:54
created

Processor::processQueryFields()   C

Complexity

Conditions 11
Paths 16

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 11

Importance

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

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 4
        }
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 19
                    $this->data = array_merge($this->data, $operationResult);
78 19
                };
79 21
            }
80
81 21
        } 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 6
                $value[$index] = $this->processQueryFields($query, $namedType, $resolvedValueItem, $value[$index]);
165 8
            }
166 8
        } 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 1
            }
205
206 1
            $value = $listValue;
207 1
        } 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
        return $field->resolve($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
226
    }
227
228
    /**
229
     * @param               $contextValue
230
     * @param FieldAst      $fieldAst
231
     * @param AbstractField $field
232
     *
233
     * @throws \Exception
234
     *
235
     * @return mixed
236
     */
237 16
    protected function getPreResolvedValue($contextValue, FieldAst $fieldAst, AbstractField $field)
238
    {
239 16
        $resolved      = false;
240 16
        $resolverValue = null;
241
242 16
        if (is_array($contextValue) && array_key_exists($fieldAst->getName(), $contextValue)) {
243 12
            $resolverValue = $contextValue[$fieldAst->getName()];
244 12
            $resolved      = true;
245 16
        } elseif (is_object($contextValue)) {
246 6
            $resolverValue = TypeService::getPropertyValue($contextValue, $fieldAst->getName());
247 6
            $resolved      = true;
248 6
        }
249
250 16
        if (!$resolved && $field->getType()->getNamedType()->getKind() == TypeMap::KIND_SCALAR) {
251 1
            $resolved = true;
252 1
        }
253
254 16
        if ($field->getConfig()->getResolveFunction()) {
255 1
            $resolveInfo   = new ResolveInfo($field, [$fieldAst], $field->getType(), $this->executionContext);
256 1
            $resolverValue = $field->resolve($resolved ? $resolverValue : $contextValue, $fieldAst->getKeyValueArguments(), $resolveInfo);
257 1
        }
258
259 16
        if (!$resolverValue && !$resolved) {
260 1
            throw new \Exception(sprintf('Property "%s" not found in resolve result', $fieldAst->getName()));
261
        }
262
263 16
        return $resolverValue;
264
    }
265
266
    /**
267
     * @param $field     AbstractField
268
     * @param $query     Query
269
     *
270
     * @return array
271
     */
272 19
    protected function parseArgumentsValues(AbstractField $field, Query $query)
273
    {
274 19
        $args = [];
275 19
        foreach ($query->getArguments() as $argument) {
276 7
            if ($configArgument = $field->getConfig()->getArgument($argument->getName())) {
277 7
                $args[$argument->getName()] = $configArgument->getType()->parseValue($argument->getValue()->getValue());
278 7
            }
279 19
        }
280
281 19
        return $args;
282
    }
283
284
    /**
285
     * @param $query         Query|FragmentInterface
286
     * @param $queryType     AbstractObjectType|TypeInterface|Field|AbstractType
287
     * @param $resolvedValue mixed
288
     * @param $value         array
289
     *
290
     * @throws \Exception
291
     *
292
     * @return array
293
     */
294 17
    protected function processQueryFields($query, AbstractType $queryType, $resolvedValue, $value)
295
    {
296 17
        foreach ($query->getFields() as $fieldAst) {
297 17
            $fieldResolvedValue = null;
298
299 17
            if ($fieldAst instanceof FragmentInterface) {
300
                /** @var TypedFragmentReference $fragment */
301 3
                $fragment = $fieldAst;
302 3
                if ($fieldAst instanceof FragmentReference) {
303
                    /** @var Fragment $fragment */
304 2
                    $fragment = $this->executionContext->getRequest()->getFragment($fieldAst->getName());
305 2
                    $this->resolveValidator->assertValidFragmentForField($fragment, $fieldAst, $queryType);
306 3
                } elseif ($fragment->getTypeName() !== $queryType->getName()) {
307 1
                    continue;
308
                }
309
310 3
                $fragmentValue      = $this->processQueryFields($fragment, $queryType, $resolvedValue, $value);
311 3
                $fieldResolvedValue = is_array($fragmentValue) ? $fragmentValue : [];
312 3
            } else {
313 17
                $alias       = $fieldAst->getAlias() ?: $fieldAst->getName();
314 17
                $currentType = $queryType->getNullableType();
315
316 17
                if ($fieldAst->getName() == self::TYPE_NAME_QUERY) {
317 1
                    $fieldResolvedValue = [$alias => $queryType->getName()];
318 17
                } elseif ($fieldAst instanceof Query) {
319 9
                    $fieldValue         = $this->processQueryAST($fieldAst, $currentType->getField($fieldAst->getName()), $resolvedValue);
320 9
                    $fieldResolvedValue = [$alias => $fieldValue];
321 17
                } elseif ($fieldAst instanceof FieldAst) {
322
323 16
                    if (!$this->resolveValidator->objectHasField($currentType, $fieldAst)) {
324 2
                        $fieldResolvedValue = null;
325 2
                    } else {
326
                        $fieldResolvedValue = [
327 16
                            $alias => $this->processFieldAST($fieldAst, $currentType->getField($fieldAst->getName()), $resolvedValue)
328 16
                        ];
329
                    }
330 16
                }
331
            }
332
333 17
            $value = $this->collectValue($value, $fieldResolvedValue);
334 17
        }
335
336 17
        return $value;
337
    }
338
339 16
    protected function getFieldValidatedValue(AbstractField $field, $value)
340
    {
341 16
        return ($this->resolveValidator->isValidValueForField($field, $value)) ? $this->getOutputValue($field->getType(), $value) : null;
342
    }
343
344 16
    protected function getOutputValue(AbstractType $type, $value)
345
    {
346 16
        return in_array($type->getKind(), [TypeMap::KIND_OBJECT, TypeMap::KIND_NON_NULL]) ? $value : $type->serialize($value);
347
    }
348
349 17
    protected function collectValue($value, $queryValue)
350
    {
351 17
        if ($queryValue && is_array($queryValue)) {
352 17
            $value = array_merge(is_array($value) ? $value : [], $queryValue);
353 17
        } else {
354 2
            $value = $queryValue;
355
        }
356
357 17
        return $value;
358
    }
359
360 21
    protected function introduceIntrospectionFields(AbstractSchema $schema)
361
    {
362 21
        $schemaField = new SchemaField();
363 21
        $schemaField->setSchema($schema);
364
365 21
        $schema->addQueryField($schemaField);
366 21
        $schema->addQueryField(new TypeDefinitionField());
367 21
    }
368
369 21
    public function getResponseData()
370
    {
371 21
        $result = [];
372
373 21
        if (!empty($this->data)) {
374 19
            $result['data'] = $this->data;
375 19
        }
376
377 21
        if ($this->executionContext->hasErrors()) {
378 6
            $result['errors'] = $this->executionContext->getErrorsArray();
379 6
        }
380
381 21
        return $result;
382
    }
383
}
384