Completed
Push — master ( 14d264...b8b70e )
by Portey
05:01
created

Processor::checkFieldExist()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.256

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 3
cts 5
cp 0.6
rs 9.4286
cc 2
eloc 5
nc 2
nop 2
crap 2.256
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\InputObjectType;
22
use Youshido\GraphQL\Type\Object\ObjectType;
23
use Youshido\GraphQL\Type\TypeInterface;
24
use Youshido\GraphQL\Type\TypeMap;
25
use Youshido\GraphQL\Validator\Exception\ResolveException;
26
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
27
28
class Processor
29
{
30
31
    /** @var  array */
32
    protected $data;
33
34
    /** @var ResolveValidatorInterface */
35
    protected $resolveValidator;
36
37
    /** @var Schema */
38
    protected $schema;
39
40
    /** @var PropertyAccessor */
41
    protected $propertyAccessor;
42
43
    /** @var Request */
44
    protected $request;
45
46 2
    public function __construct(ResolveValidatorInterface $validator)
47
    {
48 2
        $this->resolveValidator = $validator;
49
50 2
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
51 2
    }
52
53 2
    public function processQuery($queryString, $variables = [])
54
    {
55 2
        $this->resolveValidator->clearErrors();
56 2
        $this->data = [];
57
58
        try {
59 2
            $this->parseAndCreateRequest($queryString, $variables);
60
61 2
            if ($this->request->hasQueries()) {
62 2
                foreach ($this->request->getQueries() as $query) {
63 2
                    if ($queryResult = $this->executeQuery($query, $this->getSchema()->getQueryType())) {
64 1
                        $this->data = array_merge($this->data, $queryResult);
65 1
                    };
66 2
                }
67 2
            }
68
69 2
            if ($this->request->hasMutations()) {
70
                foreach ($this->request->getMutations() as $mutation) {
71
                    if ($mutationResult = $this->executeMutation($mutation, $this->getSchema()->getMutationType())) {
72
                        $this->data = array_merge($this->data, $mutationResult);
73
                    }
74
                }
75
            }
76 2
        } catch (\Exception $e) {
77
            $this->resolveValidator->clearErrors();
78
79
            $this->resolveValidator->addError($e);
80
        }
81 2
    }
82
83
    /**
84
     * @param Mutation        $mutation
85
     * @param InputObjectType $objectType
86
     *
87
     * @return array|bool|mixed
88
     */
89
    protected function executeMutation($mutation, $objectType)
90
    {
91
        if (!$this->checkFieldExist($objectType, $mutation)) {
92
93
            return null;
94
        }
95
96
        /** @var InputObjectType $inputType */
97
        $inputType = $objectType->getConfig()->getField($mutation->getName())->getType();
98
99
        if (!$this->resolveValidator->validateArguments($inputType, $mutation, $this->request)) {
100
            return null;
101
        }
102
103
        $alias         = $mutation->hasAlias() ? $mutation->getAlias() : $mutation->getName();
104
        $resolvedValue = $this->resolveValue($inputType, null, $mutation);
105
106 View Code Duplication
        if (!$this->resolveValidator->validateResolvedValue($resolvedValue, $inputType->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...
107
            $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for mutation "%s"', $inputType->getName())));
108
109
            return [$alias => null];
110
        }
111
112
        $value = null;
113
        if ($mutation->hasFields()) {
114
            $outputType = $inputType->getConfig()->getOutputType();
0 ignored issues
show
Documentation Bug introduced by
The method getOutputType does not exist on object<Youshido\GraphQL\...bject\ObjectTypeConfig>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
115
116
            $value = $this->processQueryFields($mutation, $outputType, $resolvedValue, []);
117
        }
118
119
        return [$alias => $value];
120
    }
121
122 2
    protected function parseAndCreateRequest($query, $variables = [])
123
    {
124 2
        $parser = new Parser();
125
126 2
        $parser->setSource($query);
127 2
        $data = $parser->parse();
128
129 2
        $this->request = new Request($data);
130 2
        $this->request->setVariables($variables);
131 2
    }
132
133
    /**
134
     * @param Query|Field $query
135
     * @param ObjectType  $currentLevelSchema
136
     * @param null        $contextValue
137
     * @return array|bool|mixed
138
     */
139 2
    protected function executeQuery($query, $currentLevelSchema, $contextValue = null)
140
    {
141 2
        if (!$this->checkFieldExist($currentLevelSchema, $query)) {
142
143
            return null;
144
        }
145
146
        /** @todo need to check if the correct field class is used here */
147
        /** @var ObjectType $queryType */
148 2
        $queryType = $currentLevelSchema->getConfig()->getField($query->getName())->getType();
149 2
        if (get_class($query) == 'Youshido\GraphQL\Parser\Ast\Field') {
150
            $alias            = $query->getName();
151
            $preResolvedValue = $this->getPreResolvedValue($contextValue, $query);
152
            $value            = $queryType->serialize($preResolvedValue);
153
        } else {
154 2
            if (!$this->resolveValidator->validateArguments($queryType, $query, $this->request)) {
155 1
                return null;
156
            }
157
158 1
            $resolvedValue = $this->resolveValue($queryType, $contextValue, $query);
159 1
            $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...
160
161 1 View Code Duplication
            if (!$this->resolveValidator->validateResolvedValue($resolvedValue, $currentLevelSchema->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...
162 1
                $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for query "%s"', $queryType->getName())));
163
164 1
                return [$alias => null];
165
            }
166
167
            $value = [];
168
            if ($queryType->getKind() == TypeMap::KIND_LIST) {
169
                foreach ($resolvedValue as $resolvedValueItem) {
170
                    $value[] = [];
171
                    $index   = count($value) - 1;
172
173
                    $value[$index] = $this->processQueryFields($query, $queryType, $resolvedValueItem, $value[$index]);
174
                }
175
            } else {
176
                $value = $this->processQueryFields($query, $queryType, $resolvedValue, $value);
177
            }
178
        }
179
180
        return [$alias => $value];
181
    }
182
183
    /**
184
     * @param $objectType InputObjectType|ObjectType
185
     * @param $query Mutation|Query
186
     * @return null
187
     */
188 2
    private function checkFieldExist($objectType, $query)
189
    {
190 2
        if (!$objectType->getConfig()->hasField($query->getName())) {
191
            $this->resolveValidator->addError(new ResolveException(sprintf('Field "%s" not found in schema', $query->getName())));
192
193
            return false;
194
        }
195
196 2
        return true;
197
    }
198
199
    /**
200
     * @param $value
201
     * @param $query Field
202
     *
203
     * @throws \Exception
204
     *
205
     * @return mixed
206
     */
207
    protected function getPreResolvedValue($value, $query)
208
    {
209
        if (is_array($value)) {
210
            if (array_key_exists($query->getName(), $value)) {
211
                return $value[$query->getName()];
212
            } else {
213
                throw new \Exception('Not found in resolve result', $query->getName());
214
            }
215
        } elseif (is_object($value)) {
216
            return $this->propertyAccessor->getValue($value, $query->getName());
217
        }
218
219
        return $value;
220
    }
221
222
    /**
223
     * @param $queryType    ObjectType
224
     * @param $contextValue mixed
225
     * @param $query        Query
226
     *
227
     * @return mixed
228
     */
229 1
    protected function resolveValue($queryType, $contextValue, $query)
230
    {
231 1
        return $queryType->resolve($contextValue, $this->parseArgumentsValues($queryType, $query));
232
    }
233
234
    /**
235
     * @param $queryType ObjectType
236
     * @param $query     Query
237
     *
238
     * @return array
239
     */
240 1
    public function parseArgumentsValues($queryType, $query)
241
    {
242 1
        if ($query instanceof \Youshido\GraphQL\Parser\Ast\Field) {
243
            return [];
244
        }
245
246 1
        $args      = [];
247 1
        $arguments = $queryType->getConfig()->getArguments();
248
249 1
        foreach ($query->getArguments() as $argument) {
250
            $type = $arguments[$argument->getName()]->getConfig()->getType();
251
252
            $args[$argument->getName()] = $type->parseValue($argument->getValue()->getValue());
253 1
        }
254
255 1
        return $args;
256
    }
257
258
    /**
259
     * @param $query         Query
260
     * @param $queryType     ObjectType|TypeInterface|Field
261
     * @param $resolvedValue mixed
262
     * @param $value         array
263
     *
264
     * @throws \Exception
265
     *
266
     * @return array
267
     */
268
    protected function processQueryFields($query, $queryType, $resolvedValue, $value)
269
    {
270
        foreach ($query->getFields() as $field) {
271
            if ($field instanceof FragmentReference) {
272
                if (!$fragment = $this->request->getFragment($field->getName())) {
273
                    throw new \Exception(sprintf('Fragment reference "%s" not found', $field->getName()));
274
                }
275
276
                if ($fragment->getModel() !== $queryType->getName()) {
277
                    throw new \Exception(sprintf('Fragment reference "%s" not found on model "%s"', $field->getName(), $queryType->getName()));
278
                }
279
280
                foreach ($fragment->getFields() as $fragmentField) {
281
                    $value = $this->collectValue($value, $this->executeQuery($fragmentField, $queryType, $resolvedValue));
282
                }
283
            } else {
284
                $value = $this->collectValue($value, $this->executeQuery($field, $queryType, $resolvedValue));
285
            }
286
        }
287
288
        return $value;
289
    }
290
291
    protected function collectValue($value, $queryValue)
292
    {
293
        if ($queryValue && is_array($queryValue)) {
294
            $value = array_merge($value, $queryValue);
295
        } else {
296
            $value = $queryValue;
297
        }
298
299
        return $value;
300
    }
301
302 2
    public function getSchema()
303
    {
304 2
        return $this->schema;
305
    }
306
307 2
    public function setSchema(Schema $schema)
308
    {
309 2
        $this->schema = $schema;
310
311 2
        $this->schema->addQuery('__schema', new SchemaType());
312 2
        $this->schema->addQuery('__type', new TypeDefinitionType());
313 2
    }
314
315
    /**
316
     * @return ResolveValidatorInterface
317
     */
318
    public function getResolveValidator()
319
    {
320
        return $this->resolveValidator;
321
    }
322
323 2
    public function getResponseData()
324
    {
325 2
        $result = [];
326
327 2
        if (!empty($this->data)) {
328 1
            $result['data'] = $this->data;
329 1
        }
330
331 2
        if ($this->resolveValidator->hasErrors()) {
332 2
            $result['errors'] = $this->resolveValidator->getErrorsArray();
333 2
        }
334
335 2
        return $result;
336
    }
337
}