Completed
Push — master ( d01340...7e7fa7 )
by Portey
07:36
created

Processor   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 385
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Test Coverage

Coverage 68.13%

Importance

Changes 28
Bugs 2 Features 7
Metric Value
wmc 60
c 28
b 2
f 7
lcom 1
cbo 20
dl 0
loc 385
ccs 124
cts 182
cp 0.6813
rs 2.1569

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
C processQuery() 0 29 8
B executeMutation() 0 32 6
A parseAndCreateRequest() 0 10 1
D executeQuery() 0 96 17
A checkFieldExist() 0 10 2
A getPreResolvedValue() 0 14 4
A resolveValue() 0 8 2
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   Complexity   

Complex Class

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\AbstractEnumType;
22
use Youshido\GraphQL\Type\Object\AbstractInterfaceType;
23
use Youshido\GraphQL\Type\Object\AbstractObjectType;
24
use Youshido\GraphQL\Type\Object\InputObjectType;
25
use Youshido\GraphQL\Type\Object\ObjectType;
26
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
27
use Youshido\GraphQL\Type\TypeInterface;
28
use Youshido\GraphQL\Type\TypeMap;
29
use Youshido\GraphQL\Validator\Exception\ResolveException;
30
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;
31
32
class Processor
33
{
34
35
    /** @var  array */
36
    protected $data;
37
38
    /** @var ResolveValidatorInterface */
39
    protected $resolveValidator;
40
41
    /** @var Schema */
42
    protected $schema;
43
44
    /** @var PropertyAccessor */
45
    protected $propertyAccessor;
46
47
    /** @var Request */
48
    protected $request;
49
50 12
    public function __construct(ResolveValidatorInterface $validator)
51
    {
52 12
        $this->resolveValidator = $validator;
53
54 12
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
55 12
    }
56
57 12
    public function processQuery($queryString, $variables = [])
58
    {
59 12
        $this->resolveValidator->clearErrors();
60 12
        $this->data = [];
61
62
        try {
63 12
            $this->parseAndCreateRequest($queryString, $variables);
64
65 12
            if ($this->request->hasQueries()) {
66 12
                foreach ($this->request->getQueries() as $query) {
67 12
                    if ($queryResult = $this->executeQuery($query, $this->getSchema()->getQueryType())) {
68 11
                        $this->data = array_merge($this->data, $queryResult);
69 11
                    };
70 12
                }
71 12
            }
72
73 12
            if ($this->request->hasMutations()) {
74
                foreach ($this->request->getMutations() as $mutation) {
75
                    if ($mutationResult = $this->executeMutation($mutation, $this->getSchema()->getMutationType())) {
76
                        $this->data = array_merge($this->data, $mutationResult);
77
                    }
78 1
                }
79
            }
80 12
        } catch (\Exception $e) {
81
            $this->resolveValidator->clearErrors();
82
83
            $this->resolveValidator->addError($e);
84
        }
85 12
    }
86
87
    /**
88
     * @param Mutation        $mutation
89
     * @param InputObjectType $objectType
90
     *
91
     * @return array|bool|mixed
92
     */
93
    protected function executeMutation($mutation, $objectType)
94
    {
95
        if (!$this->checkFieldExist($objectType, $mutation)) {
96
97
            return null;
98
        }
99
100
        /** @var Field $field */
101
        $field = $objectType->getConfig()->getField($mutation->getName());
102
103
        if (!$this->resolveValidator->validateArguments($field->getType(), $mutation, $this->request)) {
104
            return null;
105
        }
106
107
        $alias         = $mutation->hasAlias() ? $mutation->getAlias() : $mutation->getName();
108
        $resolvedValue = $this->resolveValue($field, null, $mutation);
109
110
        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...
111
            $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for mutation "%s"', $field->getType()->getName())));
112
113
            return [$alias => null];
114
        }
115
116
        $value = null;
117
        if ($mutation->hasFields()) {
118
            $outputType = $field->getType()->getConfig()->getOutputType();
119
120
            $value = $this->processQueryFields($mutation, $outputType, $resolvedValue, []);
121
        }
122
123
        return [$alias => $value];
124
    }
125
126 12
    protected function parseAndCreateRequest($query, $variables = [])
127
    {
128 12
        $parser = new Parser();
129
130 12
        $parser->setSource($query);
131 12
        $data = $parser->parse();
132
133 12
        $this->request = new Request($data);
134 12
        $this->request->setVariables($variables);
135 12
    }
136
137
    /**
138
     * @param Query|Field $query
139
     * @param ObjectType  $currentLevelSchema
140
     * @param null        $contextValue
141
     * @return array|bool|mixed
142
     */
143 12
    protected function executeQuery($query, $currentLevelSchema, $contextValue = null)
144
    {
145 12
        if (!$this->checkFieldExist($currentLevelSchema, $query)) {
146
147
            return null;
148
        }
149
150
        /** @todo need to check if the correct field class is used here */
151
152
        /** @var Field $field */
153 12
        $field = $currentLevelSchema->getConfig()->getField($query->getName());
154 12
        if (get_class($query) == 'Youshido\GraphQL\Parser\Ast\Field') {
155 10
            $alias            = $query->getName();
156 10
            $preResolvedValue = $this->getPreResolvedValue($contextValue, $query);
157
158 10
            if ($field->getConfig()->getType()->getKind() == TypeMap::KIND_LIST) {
159 1
                if(!is_array($preResolvedValue)){
160
                    $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...
161
                    $this->resolveValidator->addError(new ResolveException('Not valid resolve value for list type'));
162
                }
163
164
165 1
                $listValue = [];
166 1
                foreach ($preResolvedValue as $resolvedValueItem) {
0 ignored issues
show
Bug introduced by
The expression $preResolvedValue of type object|integer|double|string|null|boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
167
                    /** @var TypeInterface $type */
168 1
                    $type = $field->getType()->getConfig()->getItem();
169
170 1
                    if ($type->getKind() == TypeMap::KIND_ENUM) {
171
                        /** @var $type AbstractEnumType */
172 1
                        if(!$type->isValidValue($resolvedValueItem)) {
173
                            $this->resolveValidator->addError(new ResolveException('Not valid value for enum type'));
174
175
                            $listValue = null;
176
                            break;
177
                        }
178
179 1
                        $listValue[] = $type->resolve($resolvedValueItem);
180 1
                    } else {
181
                        /** @var AbstractScalarType $type */
182
                        $listValue[] = $type->serialize($preResolvedValue);
183
                    }
184 1
                }
185
186 1
                $value = $listValue;
187 1
            } else {
188 10
                if ($field->getType()->getKind() == TypeMap::KIND_ENUM) {
189
                    if(!$field->getType()->isValidValue($preResolvedValue)) {
190
                        $this->resolveValidator->addError(new ResolveException('Not valid value for enum type'));
191
                        $value = null;
192
                    } else {
193
                        $value = $field->getType()->resolve($preResolvedValue);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Youshido\GraphQL\Type\TypeInterface as the method resolve() does only exist in the following implementations of said interface: Youshido\GraphQL\Definition\ArgumentListType, Youshido\GraphQL\Definition\ArgumentType, Youshido\GraphQL\Definition\EnumValueListType, Youshido\GraphQL\Definition\EnumValueType, Youshido\GraphQL\Definition\FieldListType, Youshido\GraphQL\Definition\FieldType, Youshido\GraphQL\Definition\InputValueListType, Youshido\GraphQL\Definition\InputValueType, Youshido\GraphQL\Definition\InterfaceListType, Youshido\GraphQL\Definition\InterfaceType, Youshido\GraphQL\Definition\MutationType, Youshido\GraphQL\Definition\PossibleOfListType, Youshido\GraphQL\Definition\PossibleOfType, Youshido\GraphQL\Definition\QueryListType, Youshido\GraphQL\Definition\QueryType, Youshido\GraphQL\Definition\SchemaType, Youshido\GraphQL\Definition\TypeDefinitionType, Youshido\GraphQL\Type\ListType\AbstractListType, Youshido\GraphQL\Type\ListType\ListType, Youshido\GraphQL\Type\Object\AbstractEnumType, Youshido\GraphQL\Type\Ob...AbstractInputObjectType, Youshido\GraphQL\Type\Object\AbstractObjectType, Youshido\GraphQL\Type\Object\EnumType, Youshido\GraphQL\Type\Object\InputObjectType, Youshido\GraphQL\Type\Object\ObjectType, Youshido\Tests\DataProvider\UserType, Youshido\Tests\Schema\DroidType, Youshido\Tests\Schema\EpisodeEnum, Youshido\Tests\Schema\HumanType, Youshido\Tests\Schema\QueryType.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
194
                    }
195
                } else {
196 10
                    $value = $field->getType()->serialize($preResolvedValue);
197
                }
198
            }
199 10
        } else {
200 12
            if (!$this->resolveValidator->validateArguments($field->getType(), $query, $this->request)) {
201 1
                return null;
202
            }
203
204 11
            $resolvedValue = $this->resolveValue($field, $contextValue, $query);
205 11
            $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...
206
207 11
            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...
208
                $this->resolveValidator->addError(new ResolveException(sprintf('Not valid resolved value for query "%s"', $field->getType()->getName())));
209
210
                return [$alias => null];
211
            }
212
213 11
            $value = [];
214 11
            if ($resolvedValue) {
215 10
                if ($field->getType()->getKind() == TypeMap::KIND_LIST) {
216 4
                    foreach ($resolvedValue as $resolvedValueItem) {
217 4
                        $value[] = [];
218 4
                        $index   = count($value) - 1;
219
220 4
                        if($field->getConfig()->getType()->getConfig()->getItem()->getKind() == TypeMap::KIND_INTERFACE) {
221 2
                            $resolvedValueItem = $field->getConfig()->getType()->getConfig()->getItemConfig()->resolveType($resolvedValueItem);
222 2
                            $type = $field->getConfig()->getType()->getConfig()->getItem();
223 2
                        } else {
224 2
                            $type = $field->getType();
225
                        }
226
227 4
                        $value[$index] = $this->processQueryFields($query, $type, $resolvedValueItem, $value[$index]);
228 4
                    }
229 4
                } else {
230 10
                    $value = $this->processQueryFields($query, $field->getType(), $resolvedValue, $value);
231
                }
232 10
            } else {
233 3
                $value = $resolvedValue;
234
            }
235
        }
236
237 11
        return [$alias => $value];
238
    }
239
240
    /**
241
     * @param $objectType InputObjectType|ObjectType
242
     * @param $query      Mutation|Query
243
     * @return null
244
     */
245 12
    private function checkFieldExist($objectType, $query)
246
    {
247 12
        if (!$objectType->getConfig()->hasField($query->getName())) {
248
            $this->resolveValidator->addError(new ResolveException(sprintf('Field "%s" not found in schema', $query->getName())));
249
250
            return false;
251
        }
252
253 12
        return true;
254
    }
255
256
    /**
257
     * @param $value
258
     * @param $query Field
259
     *
260
     * @throws \Exception
261
     *
262
     * @return mixed
263
     */
264 10
    protected function getPreResolvedValue($value, $query)
265
    {
266 10
        if (is_array($value)) {
267 6
            if (array_key_exists($query->getName(), $value)) {
268 6
                return $value[$query->getName()];
269
            } else {
270
                throw new \Exception('Not found in resolve result', $query->getName());
271
            }
272 4
        } elseif (is_object($value)) {
273 4
            return $this->propertyAccessor->getValue($value, $query->getName());
274
        }
275
276
        return $value;
277
    }
278
279
    /**
280
     * @param $field        Field
281
     * @param $contextValue mixed
282
     * @param $query        Query
283
     *
284
     * @return mixed
285
     */
286 11
    protected function resolveValue($field, $contextValue, $query)
287
    {
288 11
        if ($field->getConfig()->getType() instanceof AbstractInterfaceType) {
289 3
            $a = 'asd';
0 ignored issues
show
Unused Code introduced by
$a 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...
290 3
        }
291
292 11
        return $field->getConfig()->resolve($contextValue, $this->parseArgumentsValues($field->getConfig()->getType(), $query));
293
    }
294
295
    /**
296
     * @param $type         TypeInterface|AbstractObjectType
297
     * @param $contextValue mixed
298
     * @param $query        Query
299
     *
300
     * @return mixed
301
     */
302
    protected function resolveValueByType($type, $contextValue, $query)
303
    {
304
        return $type->resolve($contextValue, $this->parseArgumentsValues($type, $query));
305
    }
306
307
    /**
308
     * @param $queryType AbstractObjectType
309
     * @param $query     Query
310
     *
311
     * @return array
312
     */
313 11
    public function parseArgumentsValues($queryType, $query)
314
    {
315 11
        if ($query instanceof \Youshido\GraphQL\Parser\Ast\Field) {
316
            return [];
317
        }
318
319 11
        $args      = [];
320 11
        $arguments = $queryType->getConfig()->getArguments();
321
322 11
        foreach ($query->getArguments() as $argument) {
323 4
            $type = $arguments[$argument->getName()]->getConfig()->getType();
324
325 4
            $args[$argument->getName()] = $type->parseValue($argument->getValue()->getValue());
326 11
        }
327
328 11
        return $args;
329
    }
330
331
    /**
332
     * @param $query         Query
333
     * @param $queryType     ObjectType|TypeInterface|Field
334
     * @param $resolvedValue mixed
335
     * @param $value         array
336
     *
337
     * @throws \Exception
338
     *
339
     * @return array
340
     */
341 10
    protected function processQueryFields($query, $queryType, $resolvedValue, $value)
342
    {
343 10
        foreach ($query->getFields() as $field) {
344 10
            if ($field instanceof FragmentReference) {
345
                if (!$fragment = $this->request->getFragment($field->getName())) {
346
                    throw new \Exception(sprintf('Fragment reference "%s" not found', $field->getName()));
347
                }
348
349
                if ($fragment->getModel() !== $queryType->getName()) {
350
                    throw new \Exception(sprintf('Fragment reference "%s" not found on model "%s"', $field->getName(), $queryType->getName()));
351
                }
352
353
                foreach ($fragment->getFields() as $fragmentField) {
354
                    $value = $this->collectValue($value, $this->executeQuery($fragmentField, $queryType, $resolvedValue));
355
                }
356
            } else {
357 10
                $value = $this->collectValue($value, $this->executeQuery($field, $queryType, $resolvedValue));
358
            }
359 10
        }
360
361 10
        return $value;
362
    }
363
364 10
    protected function collectValue($value, $queryValue)
365
    {
366 10
        if ($queryValue && is_array($queryValue)) {
367 10
            $value = array_merge($value, $queryValue);
368 10
        } else {
369
            $value = $queryValue;
370
        }
371
372 10
        return $value;
373
    }
374
375 12
    public function getSchema()
376
    {
377 12
        return $this->schema;
378
    }
379
380 12
    public function setSchema(Schema $schema)
381
    {
382 12
        $this->schema = $schema;
383
384 12
        $__schema = new SchemaType();
385 12
        $__schema->setSchema($schema);
386
387 12
        $__type = new TypeDefinitionType();
388 12
        $__type->setSchema($schema);
389
390 12
        $this->schema->addQuery('__schema', $__schema);
391 12
        $this->schema->addQuery('__type', $__type);
392 12
    }
393
394
    /**
395
     * @return ResolveValidatorInterface
396
     */
397
    public function getResolveValidator()
398
    {
399
        return $this->resolveValidator;
400
    }
401
402 12
    public function getResponseData()
403
    {
404 12
        $result = [];
405
406 12
        if (!empty($this->data)) {
407 11
            $result['data'] = $this->data;
408 11
        }
409
410 12
        if ($this->resolveValidator->hasErrors()) {
411 1
            $result['errors'] = $this->resolveValidator->getErrorsArray();
412 1
        }
413
414 12
        return $result;
415
    }
416
}
417