Completed
Push — master ( a6a027...f8e797 )
by Alexandr
03:36
created

ResolveValidator   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 221
Duplicated Lines 8.6 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 94.95%

Importance

Changes 22
Bugs 4 Features 3
Metric Value
wmc 43
c 22
b 4
f 3
lcom 1
cbo 14
dl 19
loc 221
rs 8.3157
ccs 94
cts 99
cp 0.9495

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A objectHasField() 0 10 4
A assertTypeImplementsInterface() 0 6 2
A hasArrayAccess() 0 4 2
B isValidValueForField() 4 19 5
A getExecutionContext() 0 4 1
A setExecutionContext() 0 4 1
D validateArguments() 15 79 15
B assertTypeInUnionTypes() 0 18 5
A assertValidFragmentForField() 0 11 3
B resolveTypeIfAbstract() 0 23 4

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 ResolveValidator 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 ResolveValidator, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Date: 01.12.15
4
 *
5
 * @author Portey Vasil <[email protected]>
6
 */
7
8
namespace Youshido\GraphQL\Validator\ResolveValidator;
9
10
use Youshido\GraphQL\Execution\Context\ExecutionContextInterface;
11
use Youshido\GraphQL\Execution\Request;
12
use Youshido\GraphQL\Field\AbstractField;
13
use Youshido\GraphQL\Field\InputField;
14
use Youshido\GraphQL\Parser\Ast\Argument;
15
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal;
16
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Variable;
17
use Youshido\GraphQL\Parser\Ast\Field as AstField;
18
use Youshido\GraphQL\Parser\Ast\Fragment;
19
use Youshido\GraphQL\Parser\Ast\FragmentReference;
20
use Youshido\GraphQL\Parser\Ast\Mutation;
21
use Youshido\GraphQL\Parser\Ast\Query;
22
use Youshido\GraphQL\Type\AbstractType;
23
use Youshido\GraphQL\Type\CompositeTypeInterface;
24
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
25
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
26
use Youshido\GraphQL\Type\ListType\AbstractListType;
27
use Youshido\GraphQL\Type\Object\AbstractObjectType;
28
use Youshido\GraphQL\Type\TypeMap;
29
use Youshido\GraphQL\Type\TypeService;
30
use Youshido\GraphQL\Type\Union\AbstractUnionType;
31
use Youshido\GraphQL\Validator\Exception\ResolveException;
32
33
class ResolveValidator implements ResolveValidatorInterface
34
{
35
36
    /** @var  ExecutionContextInterface */
37
    protected $executionContext;
38
39 29
    public function __construct(ExecutionContextInterface $executionContext)
40
    {
41 29
        $this->executionContext = $executionContext;
42 29
    }
43
44
    /**
45
     * @param AbstractObjectType      $objectType
46
     * @param Mutation|Query|AstField $field
47
     * @return null
48
     */
49 23
    public function objectHasField($objectType, $field)
50
    {
51 23
        if (!(($objectType instanceof AbstractObjectType || $objectType instanceof AbstractInputObjectType)) || !$objectType->hasField($field->getName())) {
52 3
            $this->executionContext->addError(new ResolveException(sprintf('Field "%s" not found in type "%s"', $field->getName(), $objectType->getNamedType()->getName())));
53
54 3
            return false;
55
        }
56
57 23
        return true;
58
    }
59
60
    /**
61
     * @inheritdoc
62
     */
63 23
    public function validateArguments(AbstractField $field, $query, Request $request)
64
    {
65 23
        if (!count($field->getArguments())) return true;
66
67
        $requiredArguments = array_filter($field->getArguments(), function (InputField $argument) {
68 14
            return $argument->getType()->getKind() == TypeMap::KIND_NON_NULL;
69 14
        });
70
71 14
        $withDefaultArguments = array_filter($field->getArguments(), function (InputField $argument) {
72 14
            return $argument->getConfig()->get('default') !== null;
73 14
        });
74
75 14
        foreach ($query->getArguments() as $argument) {
76 10 View Code Duplication
            if (!$field->hasArgument($argument->getName())) {
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...
77 2
                $this->executionContext->addError(new ResolveException(sprintf('Unknown argument "%s" on field "%s"', $argument->getName(), $field->getName())));
78
79 2
                return false;
80
            }
81
82
            /** @var AbstractType $argumentType */
83 10
            $originalArgumentType = $field->getArgument($argument->getName())->getType();
84 10
            $argumentType         = $field->getArgument($argument->getName())->getType()->getNullableType()->getNamedType();
85 10
            if ($argument->getValue() instanceof Variable) {
86
                /** @var Variable $variable */
87 4
                $variable = $argument->getValue();
88
89
                //todo: here validate argument
90
91 4
                if ($variable->getTypeName() !== $argumentType->getName()) {
92 2
                    $this->executionContext->addError(new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName())));
93
94 2
                    return false;
95
                }
96
97
                /** @var Variable $requestVariable */
98 3
                $requestVariable = $request->getVariable($variable->getName());
99 3 View Code Duplication
                if (!$requestVariable) {
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...
100 1
                    $this->executionContext->addError(new ResolveException(sprintf('Variable "%s" does not exist for query "%s"', $argument->getName(), $field->getName())));
101
102 1
                    return false;
103
                }
104 3
                $variable->setValue($requestVariable);
105
106
            }
107
108 9
            $values = $argument->getValue()->getValue();
109 9
            if (!$originalArgumentType instanceof AbstractListType) {
110 9
                $values = [$values];
111
            }
112 9
            foreach ($values as $value) {
113 9 View Code Duplication
                if (!$argumentType->isValidValue($argumentType->parseValue($value))) {
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...
114 1
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid type for argument "%s" in query "%s"', $argument->getName(), $field->getName())));
115
116 9
                    return false;
117
                }
118
            }
119
120 9
            if (array_key_exists($argument->getName(), $requiredArguments)) {
121 3
                unset($requiredArguments[$argument->getName()]);
122
            }
123 9
            if (array_key_exists($argument->getName(), $withDefaultArguments)) {
124 9
                unset($withDefaultArguments[$argument->getName()]);
125
            }
126
        }
127
128 13
        if (count($requiredArguments)) {
129 1
            $this->executionContext->addError(new ResolveException(sprintf('Require "%s" arguments to query "%s"', implode(', ', array_keys($requiredArguments)), $query->getName())));
130
131 1
            return false;
132
        }
133
134 12
        if (count($withDefaultArguments)) {
135 1
            foreach ($withDefaultArguments as $name => $argument) {
136 1
                $query->addArgument(new Argument($name, new Literal($argument->getConfig()->get('default'))));
137
            }
138
        }
139
140 12
        return true;
141
    }
142
143 7
    public function assertTypeImplementsInterface(AbstractType $type, AbstractInterfaceType $interface)
144
    {
145 7
        if (!$interface->isValidValue($type)) {
146 1
            throw new ResolveException('Type ' . $type->getName() . ' does not implement ' . $interface->getName());
147
        }
148 6
    }
149
150 3
    public function assertTypeInUnionTypes(AbstractType $type, AbstractUnionType $unionType)
151
    {
152 3
        $unionTypes = $unionType->getTypes();
153 3
        $valid      = false;
154 3
        if (empty($unionTypes)) return false;
155
156 3
        foreach ($unionTypes as $unionType) {
157 3
            if ($unionType->getName() == $type->getName()) {
158 2
                $valid = true;
159
160 3
                break;
161
            }
162
        }
163
164 3
        if (!$valid) {
165 2
            throw new ResolveException('Type ' . $type->getName() . ' not exist in types of ' . $unionType->getName());
166
        }
167 2
    }
168
169
    /**
170
     * @param Fragment          $fragment
171
     * @param FragmentReference $fragmentReference
172
     * @param AbstractType      $queryType
173
     *
174
     * @throws \Exception
175
     */
176 5
    public function assertValidFragmentForField(Fragment $fragment, FragmentReference $fragmentReference, AbstractType $queryType)
177
    {
178 5
        $innerType = $queryType;
179 5
        while ($innerType->isCompositeType()) {
180 1
          $innerType = $innerType->getTypeOf();
181
        }
182
183 5
        if ($fragment->getModel() !== $innerType->getName()) {
184 1
            throw new ResolveException(sprintf('Fragment reference "%s" not found on model "%s"', $fragmentReference->getName(), $queryType->getName()));
185
        }
186 4
    }
187
188 9
    public function hasArrayAccess($data)
189
    {
190 9
        return is_array($data) || $data instanceof \Traversable;
191
    }
192
193 20
    public function isValidValueForField(AbstractField $field, $value)
194
    {
195 20
        $fieldType = $field->getType();
196 20
        if ($fieldType->getKind() == TypeMap::KIND_NON_NULL && is_null($value)) {
197 2
            $this->executionContext->addError(new ResolveException(sprintf('Cannot return null for non-nullable field %s', $field->getName())));
198
199 2
            return null;
200
        } else {
201 20
            $fieldType = $this->resolveTypeIfAbstract($fieldType->getNullableType(), $value);
202
        }
203
204 20 View Code Duplication
        if (!is_null($value) && !$fieldType->isValidValue($value)) {
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...
205 2
            $this->executionContext->addError(new ResolveException(sprintf('Not valid value for %s field %s', $fieldType->getNullableType()->getKind(), $field->getName())));
206
207 2
            return null;
208
        }
209
210 20
        return true;
211
    }
212
213 20
    public function resolveTypeIfAbstract(AbstractType $type, $resolvedValue)
214
    {
215 20
        if (TypeService::isAbstractType($type)) {
216
            /** @var AbstractInterfaceType $type */
217 6
            $resolvedType = $type->resolveType($resolvedValue);
218
219 6
            if (!$resolvedType) {
220
                $this->executionContext->addError(new \Exception('Cannot resolve type'));
221
222
                return $type;
223
            }
224 6
            if ($type instanceof AbstractInterfaceType) {
225 5
                $this->assertTypeImplementsInterface($resolvedType, $type);
226
            } else {
227
                /** @var AbstractUnionType $type */
228 1
                $this->assertTypeInUnionTypes($resolvedType, $type);
229
            }
230
231 6
            return $resolvedType;
232
        }
233
234 20
        return $type;
235
    }
236
237
    /**
238
     * @return ExecutionContextInterface
239
     */
240 2
    public function getExecutionContext()
241
    {
242 2
        return $this->executionContext;
243
    }
244
245
    /**
246
     * @param ExecutionContextInterface $executionContext
247
     */
248
    public function setExecutionContext($executionContext)
249
    {
250
        $this->executionContext = $executionContext;
251
    }
252
253
}
254