Completed
Push — master ( 552805...97be4b )
by Portey
03:57
created

Processor   D

Complexity

Total Complexity 52

Size/Duplication

Total Lines 337
Duplicated Lines 2.97 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 69.54%

Importance

Changes 25
Bugs 2 Features 7
Metric Value
wmc 52
c 25
b 2
f 7
lcom 1
cbo 17
dl 10
loc 337
ccs 105
cts 151
cp 0.6954
rs 4.9781

16 Methods

Rating   Name   Duplication   Size   Complexity  
A parseAndCreateRequest() 0 10 1
A __construct() 0 6 1
C processQuery() 0 29 8
B executeMutation() 5 32 6
C executeQuery() 5 52 10
A checkFieldExist() 0 10 2
A getPreResolvedValue() 0 14 4
A resolveValue() 0 4 1
A resolveValueByType() 0 4 1
A parseArgumentsValues() 0 17 3
B processQueryFields() 0 22 6
A collectValue() 0 10 3
A getSchema() 0 4 1
A setSchema() 0 13 1
A getResolveValidator() 0 4 1
A getResponseData() 0 14 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Processor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Processor, and based on these observations, apply Extract Interface, too.

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