Completed
Pull Request — master (#31)
by Sebastian
06:26 queued 02:32
created

ResolveValidator   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 216
Duplicated Lines 8.8 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 94.79%

Importance

Changes 21
Bugs 3 Features 3
Metric Value
wmc 42
c 21
b 3
f 3
lcom 1
cbo 14
dl 19
loc 216
ccs 91
cts 96
cp 0.9479
rs 8.295

11 Methods

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