Completed
Push — master ( 97be4b...1dea1e )
by Portey
03:17
created

Processor::setSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 6
Bugs 0 Features 1
Metric Value
c 6
b 0
f 1
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 9.4286
cc 1
eloc 8
nc 1
nop 1
crap 1
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 5
    public function __construct(ResolveValidatorInterface $validator)
48
    {
49 5
        $this->resolveValidator = $validator;
50
51 5
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
52 5
    }
53
54 5
    public function processQuery($queryString, $variables = [])
55
    {
56 5
        $this->resolveValidator->clearErrors();
57 5
        $this->data = [];
58
59
        try {
60 5
            $this->parseAndCreateRequest($queryString, $variables);
61
62 5
            if ($this->request->hasQueries()) {
63 5
                foreach ($this->request->getQueries() as $query) {
64 5
                    if ($queryResult = $this->executeQuery($query, $this->getSchema()->getQueryType())) {
65 5
                        $this->data = array_merge($this->data, $queryResult);
66 5
                    };
67 5
                }
68 5
            }
69
70 5
            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 5
        } catch (\Exception $e) {
78 1
            $this->resolveValidator->clearErrors();
79
80
            $this->resolveValidator->addError($e);
81
        }
82 5
    }
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 5
    protected function parseAndCreateRequest($query, $variables = [])
124
    {
125 5
        $parser = new Parser();
126
127 5
        $parser->setSource($query);
128 5
        $data = $parser->parse();
129
130 5
        $this->request = new Request($data);
131 5
        $this->request->setVariables($variables);
132 5
    }
133
134
    /**
135
     * @param Query|Field $query
136
     * @param ObjectType  $currentLevelSchema
137
     * @param null        $contextValue
138
     * @return array|bool|mixed
139
     */
140 5
    protected function executeQuery($query, $currentLevelSchema, $contextValue = null)
141
    {
142 5
        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
149
        /** @var Field $field */
150 5
        $field = $currentLevelSchema->getConfig()->getField($query->getName());
151 5
        if (get_class($query) == 'Youshido\GraphQL\Parser\Ast\Field') {
152 4
            $alias            = $query->getName();
153 4
            $preResolvedValue = $this->getPreResolvedValue($contextValue, $query);
154 4
            $value            = $field->getType()->serialize($preResolvedValue);
155 4
        } else {
156 5
            if (!$this->resolveValidator->validateArguments($field->getType(), $query, $this->request)) {
157
                return null;
158
            }
159
160 5
            $resolvedValue = $this->resolveValue($field, $contextValue, $query);
161 5
            $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...
162
163 5 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...
164
                $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for query "%s"', $field->getType()->getName())));
165
166
                return [$alias => null];
167
            }
168
169 5
            $value = [];
170 5
            if($resolvedValue){
171 4
                if ($field->getType()->getKind() == TypeMap::KIND_LIST) {
172 2
                    foreach ($resolvedValue as $resolvedValueItem) {
173 2
                        $value[] = [];
174 2
                        $index   = count($value) - 1;
175
176 2
                        $value[$index] = $this->processQueryFields($query, $field->getType(), $resolvedValueItem, $value[$index]);
177 2
                    }
178 2
                } else {
179 4
                    $value = $this->processQueryFields($query, $field->getType(), $resolvedValue, $value);
180
                }
181 4
            } else {
182 3
                $value = $resolvedValue;
183
            }
184
        }
185
186 5
        return [$alias => $value];
187
    }
188
189
    /**
190
     * @param $objectType InputObjectType|ObjectType
191
     * @param $query Mutation|Query
192
     * @return null
193
     */
194 5
    private function checkFieldExist($objectType, $query)
195
    {
196 5
        if (!$objectType->getConfig()->hasField($query->getName())) {
197
            $this->resolveValidator->addError(new ResolveException(sprintf('Field "%s" not found in schema', $query->getName())));
198
199
            return false;
200
        }
201
202 5
        return true;
203
    }
204
205
    /**
206
     * @param $value
207
     * @param $query Field
208
     *
209
     * @throws \Exception
210
     *
211
     * @return mixed
212
     */
213 4
    protected function getPreResolvedValue($value, $query)
214
    {
215 4
        if (is_array($value)) {
216 1
            if (array_key_exists($query->getName(), $value)) {
217 1
                return $value[$query->getName()];
218
            } else {
219
                throw new \Exception('Not found in resolve result', $query->getName());
220
            }
221 3
        } elseif (is_object($value)) {
222 3
            return $this->propertyAccessor->getValue($value, $query->getName());
223
        }
224
225
        return $value;
226
    }
227
228
    /**
229
     * @param $field    Field
230
     * @param $contextValue mixed
231
     * @param $query        Query
232
     *
233
     * @return mixed
234
     */
235 5
    protected function resolveValue($field, $contextValue, $query)
236
    {
237 5
        return $field->getConfig()->resolve($contextValue, $this->parseArgumentsValues($field->getConfig()->getType(), $query));
238
    }
239
240
    /**
241
     * @param $type    TypeInterface|AbstractObjectType
242
     * @param $contextValue mixed
243
     * @param $query        Query
244
     *
245
     * @return mixed
246
     */
247
    protected function resolveValueByType($type, $contextValue, $query)
248
    {
249
        return $type->resolve($contextValue, $this->parseArgumentsValues($type, $query));
250
    }
251
252
    /**
253
     * @param $queryType AbstractObjectType
254
     * @param $query     Query
255
     *
256
     * @return array
257
     */
258 5
    public function parseArgumentsValues($queryType, $query)
259
    {
260 5
        if ($query instanceof \Youshido\GraphQL\Parser\Ast\Field) {
261
            return [];
262
        }
263
264 5
        $args      = [];
265 5
        $arguments = $queryType->getConfig()->getArguments();
266
267 5
        foreach ($query->getArguments() as $argument) {
268 1
            $type = $arguments[$argument->getName()]->getConfig()->getType();
269
270 1
            $args[$argument->getName()] = $type->parseValue($argument->getValue()->getValue());
271 5
        }
272
273 5
        return $args;
274
    }
275
276
    /**
277
     * @param $query         Query
278
     * @param $queryType     ObjectType|TypeInterface|Field
279
     * @param $resolvedValue mixed
280
     * @param $value         array
281
     *
282
     * @throws \Exception
283
     *
284
     * @return array
285
     */
286 4
    protected function processQueryFields($query, $queryType, $resolvedValue, $value)
287
    {
288 4
        foreach ($query->getFields() as $field) {
289 4
            if ($field instanceof FragmentReference) {
290
                if (!$fragment = $this->request->getFragment($field->getName())) {
291
                    throw new \Exception(sprintf('Fragment reference "%s" not found', $field->getName()));
292
                }
293
294
                if ($fragment->getModel() !== $queryType->getName()) {
295
                    throw new \Exception(sprintf('Fragment reference "%s" not found on model "%s"', $field->getName(), $queryType->getName()));
296
                }
297
298
                foreach ($fragment->getFields() as $fragmentField) {
299
                    $value = $this->collectValue($value, $this->executeQuery($fragmentField, $queryType, $resolvedValue));
300
                }
301
            } else {
302 4
                $value = $this->collectValue($value, $this->executeQuery($field, $queryType, $resolvedValue));
303
            }
304 4
        }
305
306 4
        return $value;
307
    }
308
309 4
    protected function collectValue($value, $queryValue)
310
    {
311 4
        if ($queryValue && is_array($queryValue)) {
312 4
            $value = array_merge($value, $queryValue);
313 4
        } else {
314
            $value = $queryValue;
315
        }
316
317 4
        return $value;
318
    }
319
320 5
    public function getSchema()
321
    {
322 5
        return $this->schema;
323
    }
324
325 5
    public function setSchema(Schema $schema)
326
    {
327 5
        $this->schema = $schema;
328
329 5
        $__schema = new SchemaType();
330 5
        $__schema->setSchema($schema);
331
332 5
        $__type = new TypeDefinitionType();
333 5
        $__type->setSchema($schema);
334
335 5
        $this->schema->addQuery('__schema', $__schema);
336 5
        $this->schema->addQuery('__type', $__type);
337 5
    }
338
339
    /**
340
     * @return ResolveValidatorInterface
341
     */
342
    public function getResolveValidator()
343
    {
344
        return $this->resolveValidator;
345
    }
346
347 5
    public function getResponseData()
348
    {
349 5
        $result = [];
350
351 5
        if (!empty($this->data)) {
352 5
            $result['data'] = $this->data;
353 5
        }
354
355 5
        if ($this->resolveValidator->hasErrors()) {
356
            $result['errors'] = $this->resolveValidator->getErrorsArray();
357
        }
358
359 5
        return $result;
360
    }
361
}
362