Completed
Pull Request — master (#13)
by
unknown
05:06
created

Processor::processPayload()   B

Complexity

Conditions 6
Paths 26

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 25
rs 8.439
ccs 16
cts 16
cp 1
cc 6
eloc 14
nc 26
nop 2
crap 6
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);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $fieldType. This often makes code more readable.
Loading history...
151 20
        if (is_null($resolvedValue)) return null;
152
153 18
        $value = [];
154 18
        if ($fieldType->getKind() == TypeMap::KIND_LIST) {
155 8
            foreach ((array)$resolvedValue as $resolvedValueItem) {
156 7
                $value[] = [];
157 7
                $index   = count($value) - 1;
158
159
160 7
                $namedType = $fieldType->getNamedType();
161 7
                $namedType = $this->resolveValidator->resolveTypeIfAbstract($namedType, $resolvedValueItem);
162 7
                if (!$namedType->isValidValue($resolvedValueItem)) {
163 1
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid resolve value in %s field', $query->getName())));
164 1
                    $value[$index] = null;
165 1
                    continue;
166
                }
167
168 6
                $value[$index] = $this->processQueryFields($query, $namedType, $resolvedValueItem, $value[$index]);
169 8
            }
170 8
        } else {
171 18
            if (!$query->hasFields()) {
172 2
                return $this->getOutputValue($fieldType, $resolvedValue);
173
            }
174
175 18
            $value = $this->processQueryFields($query, $fieldType, $resolvedValue, $value);
176
        }
177
178 18
        return $value;
179
    }
180
181
    /**
182
     * @param FieldAst      $fieldAst
183
     * @param AbstractField $field
184
     *
185
     * @param mixed         $contextValue
186
     * @return array|mixed|null
187
     * @throws ResolveException
188
     * @throws \Exception
189
     */
190 17
    protected function processFieldAST(FieldAst $fieldAst, AbstractField $field, $contextValue)
191
    {
192 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...
193 17
        $fieldType        = $field->getType();
194 17
        $preResolvedValue = $this->getPreResolvedValue($contextValue, $fieldAst, $field);
195
196 17
        if ($fieldType->getKind() == TypeMap::KIND_LIST) {
197 1
            $listValue = [];
198 1
            foreach ($preResolvedValue as $resolvedValueItem) {
199 1
                $type = $fieldType->getNamedType();
200
201 1
                if (!$type->isValidValue($resolvedValueItem)) {
202
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid resolve value in %s field', $field->getName())));
203
204
                    $listValue = null;
205
                    break;
206
                }
207 1
                $listValue[] = $this->getOutputValue($type, $resolvedValueItem);
208 1
            }
209
210 1
            $value = $listValue;
211 1
        } else {
212 17
            $value = $this->getFieldValidatedValue($field, $preResolvedValue);
213
        }
214
215 17
        return $value;
216
    }
217
218
    /**
219
     * @param AbstractField $field
220
     * @param mixed         $contextValue
221
     * @param Query         $query
222
     *
223
     * @return mixed
224
     */
225 20
    protected function resolveFieldValue(AbstractField $field, $contextValue, Query $query)
226
    {
227 20
        $resolveInfo = new ResolveInfo($field, $query->getFields(), $field->getType(), $this->executionContext);
228
229 20
        if ($resolveFunc = $field->getConfig()->getResolveFunction()) {
230 18
            return $resolveFunc($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
231 9
        } elseif ($propertyValue = TypeService::getPropertyValue($contextValue, $field->getName())) {
232 1
            return $propertyValue;
233
        } else {
234 8
            return $field->resolve($contextValue, $this->parseArgumentsValues($field, $query), $resolveInfo);
235
        }
236
    }
237
238
    /**
239
     * @param               $contextValue
240
     * @param FieldAst      $fieldAst
241
     * @param AbstractField $field
242
     *
243
     * @throws \Exception
244
     *
245
     * @return mixed
246
     */
247 17
    protected function getPreResolvedValue($contextValue, FieldAst $fieldAst, AbstractField $field)
248
    {
249 17
        $resolved      = false;
250 17
        $resolverValue = null;
251
252 17
        if (is_array($contextValue) && array_key_exists($fieldAst->getName(), $contextValue)) {
253 13
            $resolverValue = $contextValue[$fieldAst->getName()];
254 13
            $resolved      = true;
255 17
        } elseif (is_object($contextValue)) {
256 6
            $resolverValue = TypeService::getPropertyValue($contextValue, $fieldAst->getName());
257 6
            $resolved      = true;
258 6
        }
259
260 17
        if (!$resolved && $field->getType()->getNamedType()->getKind() == TypeMap::KIND_SCALAR) {
261 2
            $resolved = true;
262 2
        }
263
264 17
        if ($resolveFunction = $field->getConfig()->getResolveFunction()) {
265 2
            $resolveInfo = new ResolveInfo($field, [$fieldAst], $field->getType(), $this->executionContext);
266
267 2
            if (!$this->resolveValidator->validateArguments($field, $fieldAst, $this->executionContext->getRequest())) {
268
                throw new \Exception(sprintf('Not valid arguments for the field "%s"', $fieldAst->getName()));
269
270
            } else {
271 2
                $resolverValue = $resolveFunction($resolved ? $resolverValue : $contextValue, $fieldAst->getKeyValueArguments(), $resolveInfo);
272
            }
273
274 2
        }
275
276 17
        if (!$resolverValue && !$resolved) {
277 1
            throw new \Exception(sprintf('Property "%s" not found in resolve result', $fieldAst->getName()));
278
        }
279
280 17
        return $resolverValue;
281
    }
282
283
    /**
284
     * @param $field     AbstractField
285
     * @param $query     Query
286
     *
287
     * @return array
288
     */
289 20
    protected function parseArgumentsValues(AbstractField $field, Query $query)
290
    {
291 20
        $args = [];
292 20
        foreach ($query->getArguments() as $argument) {
293 8
            if ($configArgument = $field->getConfig()->getArgument($argument->getName())) {
294 8
                $args[$argument->getName()] = $configArgument->getType()->parseValue($argument->getValue()->getValue());
295 8
            }
296 20
        }
297
298 20
        return $args;
299
    }
300
301
    /**
302
     * @param $query         Query|FragmentInterface
303
     * @param $queryType     AbstractObjectType|TypeInterface|Field|AbstractType
304
     * @param $resolvedValue mixed
305
     * @param $value         array
306
     *
307
     * @throws \Exception
308
     *
309
     * @return array
310
     */
311 18
    protected function processQueryFields($query, AbstractType $queryType, $resolvedValue, $value)
312
    {
313 18
        foreach ($query->getFields() as $fieldAst) {
314 18
            $fieldResolvedValue = null;
315
316 18
            if ($fieldAst instanceof FragmentInterface) {
317
                /** @var TypedFragmentReference $fragment */
318 3
                $fragment = $fieldAst;
319 3
                if ($fieldAst instanceof FragmentReference) {
320
                    /** @var Fragment $fragment */
321 2
                    $fragment = $this->executionContext->getRequest()->getFragment($fieldAst->getName());
322 2
                    $this->resolveValidator->assertValidFragmentForField($fragment, $fieldAst, $queryType);
323 3
                } elseif ($fragment->getTypeName() !== $queryType->getName()) {
324 1
                    continue;
325
                }
326
327 3
                $fragmentValue      = $this->processQueryFields($fragment, $queryType, $resolvedValue, $value);
328 3
                $fieldResolvedValue = is_array($fragmentValue) ? $fragmentValue : [];
329 3
            } else {
330 18
                $alias       = $fieldAst->getAlias() ?: $fieldAst->getName();
331 18
                $currentType = $queryType->getNullableType();
332
333 18
                if ($fieldAst->getName() == self::TYPE_NAME_QUERY) {
334 1
                    $fieldResolvedValue = [$alias => $queryType->getName()];
335 1
                } else {
336 18
                    if (!$this->resolveValidator->objectHasField($currentType, $fieldAst)) {
337 2
                        $fieldResolvedValue = null;
338 2
                    } else {
339 18
                        if ($fieldAst instanceof Query) {
340 10
                            $queryAst           = $currentType->getField($fieldAst->getName());
341 10
                            $fieldValue         = $queryAst ? $this->processQueryAST($fieldAst, $queryAst, $resolvedValue) : null;
342 10
                            $fieldResolvedValue = [$alias => $fieldValue];
343 18
                        } elseif ($fieldAst instanceof FieldAst) {
344
                            $fieldResolvedValue = [
345 17
                                $alias => $this->processFieldAST($fieldAst, $currentType->getField($fieldAst->getName()), $resolvedValue)
346 17
                            ];
347 17
                        }
348
                    }
349
350
351
                }
352
            }
353
354 18
            $value = $this->collectValue($value, $fieldResolvedValue);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $value. This often makes code more readable.
Loading history...
355 18
        }
356
357 18
        return $value;
358
    }
359
360 17
    protected function getFieldValidatedValue(AbstractField $field, $value)
361
    {
362 17
        return ($this->resolveValidator->isValidValueForField($field, $value)) ? $this->getOutputValue($field->getType(), $value) : null;
363
    }
364
365 17
    protected function getOutputValue(AbstractType $type, $value)
366
    {
367 17
        return in_array($type->getKind(), [TypeMap::KIND_OBJECT, TypeMap::KIND_NON_NULL]) ? $value : $type->serialize($value);
368
    }
369
370 18
    protected function collectValue($value, $queryValue)
371
    {
372 18
        if ($queryValue && is_array($queryValue)) {
373 18
            $value = array_merge(is_array($value) ? $value : [], $queryValue);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $value. This often makes code more readable.
Loading history...
374 18
        } else {
375 2
            $value = $queryValue;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $value. This often makes code more readable.
Loading history...
376
        }
377
378 18
        return $value;
379
    }
380
381 22
    protected function introduceIntrospectionFields(AbstractSchema $schema)
382
    {
383 22
        $schemaField = new SchemaField();
384 22
        $schemaField->setSchema($schema);
385
386 22
        $schema->addQueryField($schemaField);
387 22
        $schema->addQueryField(new TypeDefinitionField());
388 22
    }
389
390 22
    public function getResponseData()
391
    {
392 22
        $result = [];
393
394 22
        if (!empty($this->data)) {
395 20
            $result['data'] = $this->data;
396 20
        }
397
398 22
        if ($this->executionContext->hasErrors()) {
399 6
            $result['errors'] = $this->executionContext->getErrorsArray();
400 6
        }
401
402 22
        return $result;
403
    }
404
}
405