Completed
Push — master ( e13501...8c19ea )
by Alexandr
05:24
created

ResolveValidator::validateArguments()   D

Complexity

Conditions 19
Paths 208

Size

Total Lines 74
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 19.0733

Importance

Changes 5
Bugs 1 Features 3
Metric Value
dl 0
loc 74
ccs 48
cts 51
cp 0.9412
rs 4.8035
c 5
b 1
f 3
cc 19
eloc 41
nc 208
nop 3
crap 19.0733

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\FieldInterface;
13
use Youshido\GraphQL\Field\InputField;
14
use Youshido\GraphQL\Parser\Ast\Argument;
15
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList;
16
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject;
17
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal;
18
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Variable;
19
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
20
use Youshido\GraphQL\Parser\Ast\Field as AstField;
21
use Youshido\GraphQL\Parser\Ast\Fragment;
22
use Youshido\GraphQL\Parser\Ast\FragmentReference;
23
use Youshido\GraphQL\Parser\Ast\Mutation;
24
use Youshido\GraphQL\Parser\Ast\Query;
25
use Youshido\GraphQL\Type\AbstractType;
26
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
27
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
28
use Youshido\GraphQL\Type\ListType\AbstractListType;
29
use Youshido\GraphQL\Type\Object\AbstractObjectType;
30
use Youshido\GraphQL\Type\TypeInterface;
31
use Youshido\GraphQL\Type\TypeMap;
32
use Youshido\GraphQL\Type\TypeService;
33
use Youshido\GraphQL\Type\Union\AbstractUnionType;
34
use Youshido\GraphQL\Validator\Exception\ResolveException;
35
36
class ResolveValidator implements ResolveValidatorInterface
37
{
38
39
    /** @var  ExecutionContextInterface */
40
    protected $executionContext;
41
42 38
    public function __construct(ExecutionContextInterface $executionContext)
43
    {
44 38
        $this->executionContext = $executionContext;
45 38
    }
46
47
    /**
48
     * @param AbstractObjectType      $objectType
49
     * @param Mutation|Query|AstField $field
50
     *
51
     * @return null
52
     */
53 30
    public function objectHasField($objectType, $field)
54
    {
55 30
        if (!(($objectType instanceof AbstractObjectType || $objectType instanceof AbstractInputObjectType)) || !$objectType->hasField($field->getName())) {
56 2
            $this->executionContext->addError(new ResolveException(sprintf('Field "%s" not found in type "%s"', $field->getName(), $objectType->getNamedType()->getName())));
57
58 2
            return false;
59
        }
60
61 30
        return true;
62
    }
63
64
    /**
65
     * @inheritdoc
66
     */
67 30
    public function validateArguments(FieldInterface $field, $query, Request $request)
68
    {
69
        $requiredArguments = array_filter($field->getArguments(), function (InputField $argument) {
70 22
            return $argument->getType()->getKind() == TypeMap::KIND_NON_NULL;
71 30
        });
72
73 30
        $withDefaultArguments = array_filter($field->getArguments(), function (InputField $argument) {
74 22
            return $argument->getConfig()->get('default') !== null;
75 30
        });
76
77 30
        foreach ($query->getArguments() as $argument) {
78 18
            if (!$field->hasArgument($argument->getName())) {
79 2
                $this->executionContext->addError(new ResolveException(sprintf('Unknown argument "%s" on field "%s"', $argument->getName(), $field->getName())));
80
81 2
                return false;
82
            }
83
84 18
            $originalArgumentType = $field->getArgument($argument->getName())->getType();
85 18
            $argumentType         = $originalArgumentType->getNullableType()->getNamedType();
86
87 18
            if ($argument->getValue() instanceof VariableReference) {
88 4
                if (!$this->processVariable($argument, $argumentType, $field, $request)) {
89 2
                    return false;
90
                }
91 17
            } elseif ($argumentType->getKind() == TypeMap::KIND_INPUT_OBJECT) {
92 2
                if ($originalArgumentType->getNullableType()->getKind() == TypeMap::KIND_LIST) {
93 1
                    if (!$argument->getValue() instanceof InputList) {
94
                        return false;
95
                    }
96
97 1
                    foreach ($argument->getValue()->getValue() as $item) {
98 1
                        if (!$this->processInputObject($item, $argumentType, $request)) {
0 ignored issues
show
Compatibility introduced by
$argumentType of type object<Youshido\GraphQL\Type\AbstractType> is not a sub-type of object<Youshido\GraphQL\...bstractInputObjectType>. It seems like you assume a child class of the class Youshido\GraphQL\Type\AbstractType to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
99
                            return false;
100
                        }
101 1
                    }
102 2
                } else if (!$this->processInputObject($argument->getValue(), $argumentType, $request)) {
0 ignored issues
show
Compatibility introduced by
$argumentType of type object<Youshido\GraphQL\Type\AbstractType> is not a sub-type of object<Youshido\GraphQL\...bstractInputObjectType>. It seems like you assume a child class of the class Youshido\GraphQL\Type\AbstractType to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
103
                    return false;
104
                }
105 2
            }
106
107 17
            $values = $argument->getValue()->getValue();
108 17
            if (!$originalArgumentType instanceof AbstractListType) {
109 16
                $values = [$values];
110 16
            }
111 17
            foreach ($values as $value) {
112 17
                if (!$argumentType->isValidValue($value)) {
113 4
                    $this->executionContext->addError(new ResolveException(sprintf('Not valid type for argument "%s" in query "%s"', $argument->getName(), $field->getName())));
114
115 4
                    return false;
116
                }
117 17
            }
118
119 16
            if (array_key_exists($argument->getName(), $requiredArguments)) {
120 4
                unset($requiredArguments[$argument->getName()]);
121 4
            }
122 16
            if (array_key_exists($argument->getName(), $withDefaultArguments)) {
123 6
                unset($withDefaultArguments[$argument->getName()]);
124 6
            }
125 28
        }
126
127 28
        if (count($requiredArguments)) {
128 2
            $this->executionContext->addError(new ResolveException(sprintf('Require "%s" arguments to query "%s"', implode(', ', array_keys($requiredArguments)), $query->getName())));
129
130 2
            return false;
131
        }
132
133 27
        if (count($withDefaultArguments)) {
134 4
            foreach ($withDefaultArguments as $name => $argument) {
135 4
                $query->addArgument(new Argument($name, new Literal($argument->getConfig()->get('default'))));
136 4
            }
137 4
        }
138
139 27
        return true;
140
    }
141
142 2
    private function processInputObject(InputObject $astObject, AbstractInputObjectType $inputObjectType, $request)
143
    {
144 2
        foreach ($astObject->getValue() as $name => $value) {
145 2
            $field = $inputObjectType->getField($name);
146
147 2
            if (!$field) {
148
                return false;
149
            }
150
151 2
            $argumentType = $field->getType()->getNullableType();
152
153 2
            if ($value instanceof VariableReference && !$this->processVariable(new Argument($name, $value), $argumentType, $field, $request)) {
154
                return false;
155
            }
156 2
        }
157
158 2
        return true;
159
    }
160
161 4
    private function processVariable(Argument $argument, TypeInterface $argumentType, FieldInterface $field, Request $request)
162
    {
163
        /** @var VariableReference $variableReference */
164 4
        $variableReference = $argument->getValue();
165
166
        /** @var Variable $variable */
167 4
        $variable = $variableReference->getVariable();
168
169
        //todo: here validate argument
170
171 4
        if ($variable->getTypeName() !== $argumentType->getName()) {
172 2
            $this->executionContext->addError(new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName())));
173
174 2
            return false;
175
        }
176
177
        /** @var Variable $requestVariable */
178 3
        $requestVariable = $request->getVariable($variable->getName());
179 3
        if (!$request->hasVariable($variable->getName()) || (null === $requestVariable && $variable->isNullable())) {
180 1
            $this->executionContext->addError(new ResolveException(sprintf('Variable "%s" does not exist for query "%s"', $argument->getName(), $field->getName())));
181
182 1
            return false;
183
        }
184
185 3
        $variableReference->setValue($requestVariable);
186
187 3
        return true;
188
    }
189
190 7
    public function assertTypeImplementsInterface(AbstractType $type, AbstractInterfaceType $interface)
191
    {
192 7
        if (!$interface->isValidValue($type)) {
193 1
            throw new ResolveException('Type ' . $type->getName() . ' does not implement ' . $interface->getName());
194
        }
195 6
    }
196
197 3
    public function assertTypeInUnionTypes(AbstractType $type, AbstractUnionType $unionType)
198
    {
199 3
        $unionTypes = $unionType->getTypes();
200 3
        $valid      = false;
201 3
        if (empty($unionTypes)) return false;
202
203 3
        foreach ($unionTypes as $unionType) {
204 3
            if ($unionType->getName() == $type->getName()) {
205 2
                $valid = true;
206
207 2
                break;
208
            }
209 3
        }
210
211 3
        if (!$valid) {
212 2
            throw new ResolveException('Type ' . $type->getName() . ' not exist in types of ' . $unionType->getName());
213
        }
214 2
    }
215
216
    /**
217
     * @param Fragment          $fragment
218
     * @param FragmentReference $fragmentReference
219
     * @param AbstractType      $queryType
220
     *
221
     * @throws \Exception
222
     */
223 5
    public function assertValidFragmentForField($fragment, FragmentReference $fragmentReference, AbstractType $queryType)
224
    {
225 5
        $innerType = $queryType;
226 5
        while ($innerType->isCompositeType()) {
227 3
            $innerType = $innerType->getTypeOf();
228 3
        }
229
230 5
        if (!$fragment instanceof Fragment || $fragment->getModel() !== $innerType->getName()) {
231 1
            throw new ResolveException(sprintf('Fragment reference "%s" not found on model "%s"', $fragmentReference->getName(), $queryType->getName()));
232
        }
233 4
    }
234
235 10
    public function hasArrayAccess($data)
236
    {
237 10
        return is_array($data) || $data instanceof \Traversable;
238
    }
239
240 26
    public function isValidValueForField(FieldInterface $field, $value)
241
    {
242 26
        $fieldType = $field->getType();
243 26 View Code Duplication
        if ($fieldType->getKind() == TypeMap::KIND_NON_NULL && is_null($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...
244 2
            $this->executionContext->addError(new ResolveException(sprintf('Cannot return null for non-nullable field %s', $field->getName())));
245
246 2
            return null;
247
        } else {
248 26
            $fieldType = $this->resolveTypeIfAbstract($fieldType->getNullableType(), $value);
249
        }
250
251 26
        if (!is_null($value) && !$fieldType->isValidValue($value)) {
252 2
            $this->executionContext->addError(new ResolveException(sprintf('Not valid value for %s field %s', $fieldType->getNullableType()->getKind(), $field->getName())));
253
254 2
            return null;
255
        }
256
257 26
        return true;
258
    }
259
260 26
    public function resolveTypeIfAbstract(AbstractType $type, $resolvedValue)
261
    {
262 26
        return TypeService::isAbstractType($type) ? $this->resolveAbstractType($type, $resolvedValue) : $type;
263
    }
264
265
    /**
266
     * @param AbstractType $type
267
     * @param              $resolvedValue
268
     *
269
     * @return AbstractObjectType
270
     * @throws ResolveException
271
     */
272 6
    public function resolveAbstractType(AbstractType $type, $resolvedValue)
273
    {
274
        /** @var AbstractInterfaceType $type */
275 6
        $resolvedType = $type->resolveType($resolvedValue);
276
277 6
        if (!$resolvedType) {
278
            $this->executionContext->addError(new \Exception('Cannot resolve type'));
279
280
            return $type;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $type; (Youshido\GraphQL\Type\In...e\AbstractInterfaceType) is incompatible with the return type documented by Youshido\GraphQL\Validat...or::resolveAbstractType of type Youshido\GraphQL\Type\Object\AbstractObjectType.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
281
        }
282 6
        if ($type instanceof AbstractInterfaceType) {
283 5
            $this->assertTypeImplementsInterface($resolvedType, $type);
284 5
        } else {
285
            /** @var AbstractUnionType $type */
286 1
            $this->assertTypeInUnionTypes($resolvedType, $type);
287
        }
288
289 6
        return $resolvedType;
290
    }
291
292
    /**
293
     * @return ExecutionContextInterface
294
     */
295 2
    public function getExecutionContext()
296
    {
297 2
        return $this->executionContext;
298
    }
299
300
    /**
301
     * @param ExecutionContextInterface $executionContext
302
     */
303
    public function setExecutionContext($executionContext)
304
    {
305
        $this->executionContext = $executionContext;
306
    }
307
308
}
309