Completed
Push — master ( b8b70e...552805 )
by Portey
03:44
created

Processor::parseArgumentsValues()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 17
ccs 9
cts 10
cp 0.9
rs 9.4286
cc 3
eloc 9
nc 3
nop 2
crap 3.009
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;
11
12
use Symfony\Component\PropertyAccess\PropertyAccess;
13
use Symfony\Component\PropertyAccess\PropertyAccessor;
14
use Youshido\GraphQL\Definition\SchemaType;
15
use Youshido\GraphQL\Definition\TypeDefinitionType;
16
use Youshido\GraphQL\Parser\Ast\FragmentReference;
17
use Youshido\GraphQL\Parser\Ast\Mutation;
18
use Youshido\GraphQL\Parser\Ast\Query;
19
use Youshido\GraphQL\Parser\Parser;
20
use Youshido\GraphQL\Type\Field\Field;
21
use Youshido\GraphQL\Type\Object\AbstractObjectType;
22
use Youshido\GraphQL\Type\Object\InputObjectType;
23
use Youshido\GraphQL\Type\Object\ObjectType;
24
use Youshido\GraphQL\Type\TypeInterface;
25
use Youshido\GraphQL\Type\TypeMap;
26
use Youshido\GraphQL\Validator\Exception\ResolveException;
27
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
28
29
class Processor
30
{
31
32
    /** @var  array */
33
    protected $data;
34
35
    /** @var ResolveValidatorInterface */
36
    protected $resolveValidator;
37
38
    /** @var Schema */
39
    protected $schema;
40
41
    /** @var PropertyAccessor */
42
    protected $propertyAccessor;
43
44
    /** @var Request */
45
    protected $request;
46
47 3
    public function __construct(ResolveValidatorInterface $validator)
48
    {
49 3
        $this->resolveValidator = $validator;
50
51 3
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
52 3
    }
53
54 3
    public function processQuery($queryString, $variables = [])
55
    {
56 3
        $this->resolveValidator->clearErrors();
57 3
        $this->data = [];
58
59
        try {
60 3
            $this->parseAndCreateRequest($queryString, $variables);
61
62 3
            if ($this->request->hasQueries()) {
63 3
                foreach ($this->request->getQueries() as $query) {
64 3
                    if ($queryResult = $this->executeQuery($query, $this->getSchema()->getQueryType())) {
65 3
                        $this->data = array_merge($this->data, $queryResult);
66 3
                    };
67 3
                }
68 3
            }
69
70 3
            if ($this->request->hasMutations()) {
71
                foreach ($this->request->getMutations() as $mutation) {
72
                    if ($mutationResult = $this->executeMutation($mutation, $this->getSchema()->getMutationType())) {
73
                        $this->data = array_merge($this->data, $mutationResult);
74
                    }
75
                }
76
            }
77 3
        } catch (\Exception $e) {
78 1
            $this->resolveValidator->clearErrors();
79
80
            $this->resolveValidator->addError($e);
81
        }
82 3
    }
83
84
    /**
85
     * @param Mutation        $mutation
86
     * @param InputObjectType $objectType
87
     *
88
     * @return array|bool|mixed
89
     */
90
    protected function executeMutation($mutation, $objectType)
91
    {
92
        if (!$this->checkFieldExist($objectType, $mutation)) {
93
94
            return null;
95
        }
96
97
        /** @var Field $field */
98
        $field = $objectType->getConfig()->getField($mutation->getName());
99
100
        if (!$this->resolveValidator->validateArguments($field->getType(), $mutation, $this->request)) {
101
            return null;
102
        }
103
104
        $alias         = $mutation->hasAlias() ? $mutation->getAlias() : $mutation->getName();
105
        $resolvedValue = $this->resolveValue($field, null, $mutation);
106
107 View Code Duplication
        if (!$this->resolveValidator->validateResolvedValue($resolvedValue, $field->getType()->getKind())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
            $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for mutation "%s"', $field->getType()->getName())));
109
110
            return [$alias => null];
111
        }
112
113
        $value = null;
114
        if ($mutation->hasFields()) {
115
            $outputType = $field->getType()->getConfig()->getOutputType();
116
117
            $value = $this->processQueryFields($mutation, $outputType, $resolvedValue, []);
118
        }
119
120
        return [$alias => $value];
121
    }
122
123 3
    protected function parseAndCreateRequest($query, $variables = [])
124
    {
125 3
        $parser = new Parser();
126
127 3
        $parser->setSource($query);
128 3
        $data = $parser->parse();
129
130 3
        $this->request = new Request($data);
131 3
        $this->request->setVariables($variables);
132 3
    }
133
134
    /**
135
     * @param Query|Field $query
136
     * @param ObjectType  $currentLevelSchema
137
     * @param null        $contextValue
138
     * @return array|bool|mixed
139
     */
140 3
    protected function executeQuery($query, $currentLevelSchema, $contextValue = null)
141
    {
142 3
        if (!$this->checkFieldExist($currentLevelSchema, $query)) {
143
144
            return null;
145
        }
146
147
        /** @todo need to check if the correct field class is used here */
148
        /** @var Field $field */
149 3
        $field = $currentLevelSchema->getConfig()->getField($query->getName());
150 3
        if (get_class($query) == 'Youshido\GraphQL\Parser\Ast\Field') {
151 2
            $alias            = $query->getName();
152 2
            $preResolvedValue = $this->getPreResolvedValue($contextValue, $query);
153 2
            $value            = $field->getType()->serialize($preResolvedValue);
154 2
        } else {
155 3
            if (!$this->resolveValidator->validateArguments($field->getType(), $query, $this->request)) {
156
                return null;
157
            }
158
159 3
            $resolvedValue = $this->resolveValue($field, $contextValue, $query);
160 3
            $alias         = $query->hasAlias() ? $query->getAlias() : $query->getName();
0 ignored issues
show
Bug introduced by
The method hasAlias does only exist in Youshido\GraphQL\Parser\Ast\Query, but not in Youshido\GraphQL\Type\Field\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Bug introduced by
The method getAlias does only exist in Youshido\GraphQL\Parser\Ast\Query, but not in Youshido\GraphQL\Type\Field\Field.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
161
162 3 View Code Duplication
            if (!$this->resolveValidator->validateResolvedValue($resolvedValue, $field->getType()->getKind())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
                $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for query "%s"', $field->getType()->getName())));
164
165
                return [$alias => null];
166
            }
167
168 3
            $value = [];
169 3
            if($resolvedValue){
170 2
                if ($field->getType()->getKind() == TypeMap::KIND_LIST) {
171
                    foreach ($resolvedValue as $resolvedValueItem) {
172
                        $value[] = [];
173
                        $index   = count($value) - 1;
174
175
                        $value[$index] = $this->processQueryFields($query, $field->getType(), $resolvedValueItem, $value[$index]);
176
                    }
177
                } else {
178 2
                    $value = $this->processQueryFields($query, $field->getType(), $resolvedValue, $value);
179
                }
180 2
            } else {
181 2
                $value = $resolvedValue;
182
            }
183
        }
184
185 3
        return [$alias => $value];
186
    }
187
188
    /**
189
     * @param $objectType InputObjectType|ObjectType
190
     * @param $query Mutation|Query
191
     * @return null
192
     */
193 3
    private function checkFieldExist($objectType, $query)
194
    {
195 3
        if (!$objectType->getConfig()->hasField($query->getName())) {
196
            $this->resolveValidator->addError(new ResolveException(sprintf('Field "%s" not found in schema', $query->getName())));
197
198
            return false;
199
        }
200
201 3
        return true;
202
    }
203
204
    /**
205
     * @param $value
206
     * @param $query Field
207
     *
208
     * @throws \Exception
209
     *
210
     * @return mixed
211
     */
212 2
    protected function getPreResolvedValue($value, $query)
213
    {
214 2
        if (is_array($value)) {
215 1
            if (array_key_exists($query->getName(), $value)) {
216 1
                return $value[$query->getName()];
217
            } else {
218
                throw new \Exception('Not found in resolve result', $query->getName());
219
            }
220 1
        } elseif (is_object($value)) {
221 1
            return $this->propertyAccessor->getValue($value, $query->getName());
222
        }
223
224
        return $value;
225
    }
226
227
    /**
228
     * @param $field    Field
229
     * @param $contextValue mixed
230
     * @param $query        Query
231
     *
232
     * @return mixed
233
     */
234 3
    protected function resolveValue($field, $contextValue, $query)
235
    {
236 3
        return $field->getConfig()->resolve($contextValue, $this->parseArgumentsValues($field->getConfig()->getType(), $query));
237
    }
238
239
    /**
240
     * @param $queryType AbstractObjectType
241
     * @param $query     Query
242
     *
243
     * @return array
244
     */
245 3
    public function parseArgumentsValues($queryType, $query)
246
    {
247 3
        if ($query instanceof \Youshido\GraphQL\Parser\Ast\Field) {
248
            return [];
249
        }
250
251 3
        $args      = [];
252 3
        $arguments = $queryType->getConfig()->getArguments();
253
254 3
        foreach ($query->getArguments() as $argument) {
255 1
            $type = $arguments[$argument->getName()]->getConfig()->getType();
256
257 1
            $args[$argument->getName()] = $type->parseValue($argument->getValue()->getValue());
258 3
        }
259
260 3
        return $args;
261
    }
262
263
    /**
264
     * @param $query         Query
265
     * @param $queryType     ObjectType|TypeInterface|Field
266
     * @param $resolvedValue mixed
267
     * @param $value         array
268
     *
269
     * @throws \Exception
270
     *
271
     * @return array
272
     */
273 2
    protected function processQueryFields($query, $queryType, $resolvedValue, $value)
274
    {
275 2
        foreach ($query->getFields() as $field) {
276 2
            if ($field instanceof FragmentReference) {
277
                if (!$fragment = $this->request->getFragment($field->getName())) {
278
                    throw new \Exception(sprintf('Fragment reference "%s" not found', $field->getName()));
279
                }
280
281
                if ($fragment->getModel() !== $queryType->getName()) {
282
                    throw new \Exception(sprintf('Fragment reference "%s" not found on model "%s"', $field->getName(), $queryType->getName()));
283
                }
284
285
                foreach ($fragment->getFields() as $fragmentField) {
286
                    $value = $this->collectValue($value, $this->executeQuery($fragmentField, $queryType, $resolvedValue));
287
                }
288
            } else {
289 2
                $value = $this->collectValue($value, $this->executeQuery($field, $queryType, $resolvedValue));
290
            }
291 2
        }
292
293 2
        return $value;
294
    }
295
296 2
    protected function collectValue($value, $queryValue)
297
    {
298 2
        if ($queryValue && is_array($queryValue)) {
299 2
            $value = array_merge($value, $queryValue);
300 2
        } else {
301
            $value = $queryValue;
302
        }
303
304 2
        return $value;
305
    }
306
307 3
    public function getSchema()
308
    {
309 3
        return $this->schema;
310
    }
311
312 3
    public function setSchema(Schema $schema)
313
    {
314 3
        $this->schema = $schema;
315
316 3
        $__schema = new SchemaType();
317 3
        $__schema->setSchema($schema);
318
319 3
        $__type = new TypeDefinitionType();
320 3
        $__type->setSchema($schema);
321
322 3
        $this->schema->addQuery('__schema', $__schema);
323 3
        $this->schema->addQuery('__type', $__type);
324 3
    }
325
326
    /**
327
     * @return ResolveValidatorInterface
328
     */
329
    public function getResolveValidator()
330
    {
331
        return $this->resolveValidator;
332
    }
333
334 3
    public function getResponseData()
335
    {
336 3
        $result = [];
337
338 3
        if (!empty($this->data)) {
339 3
            $result['data'] = $this->data;
340 3
        }
341
342 3
        if ($this->resolveValidator->hasErrors()) {
343
            $result['errors'] = $this->resolveValidator->getErrorsArray();
344
        }
345
346 3
        return $result;
347
    }
348
}
349