Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Pull Request — master (#536)
by
unknown
20:27
created

InputValidator::buildValidationTree()   C

Complexity

Conditions 17
Paths 86

Size

Total Lines 69
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 28.5706

Importance

Changes 0
Metric Value
eloc 40
c 0
b 0
f 0
dl 0
loc 69
ccs 25
cts 38
cp 0.6579
rs 5.2166
cc 17
nc 86
nop 3
crap 28.5706

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
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Validator;
6
7
use GraphQL\Type\Definition\InputObjectType;
8
use GraphQL\Type\Definition\ObjectType;
9
use GraphQL\Type\Definition\ResolveInfo;
10
use GraphQL\Type\Definition\Type;
11
use Overblog\GraphQLBundle\Exception\ArgumentsValidationException;
12
use Overblog\GraphQLBundle\Validator\Mapping\MetadataFactory;
13
use Overblog\GraphQLBundle\Validator\Mapping\ObjectMetadata;
14
use RuntimeException;
15
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
16
use Symfony\Component\Validator\Constraint;
17
use Symfony\Component\Validator\Constraints\GroupSequence;
18
use Symfony\Component\Validator\Constraints\Valid;
19
use Symfony\Component\Validator\ConstraintViolationListInterface;
20
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
21
use Symfony\Component\Validator\Mapping\GetterMetadata;
22
use Symfony\Component\Validator\Mapping\PropertyMetadata;
23
use Symfony\Component\Validator\Validator\ValidatorInterface;
24
25
/**
26
 * InputValidator
27
 *
28
 * @author Timur Murtukov <[email protected]>
29
 */
30
class InputValidator
31
{
32
    private const TYPE_PROPERTY = 'property';
33
    private const TYPE_GETTER   = 'getter';
34
35
    /**
36
     * @var array
37
     */
38
    private $resolverArgs;
39
40
    /**
41
     * @var array
42
     */
43
    private $constraintMapping;
44
45
    /**
46
     * @var ValidatorInterface
47
     */
48
    private $validator;
49
50
    /**
51
     * @var MetadataFactory
52
     */
53
    private $metadataFactory;
54
55
    /**
56
     * @var ResolveInfo
57
     */
58
    private $info;
59
60
    /**
61
     * @var ValidatorFactory
62
     */
63
    private $validatorFactory;
64
65
    /**
66
     * @var ClassMetadataInterface[]
67
     */
68
    private $cachedMetadata = [];
69
70
    /**
71
     * InputValidator constructor.
72
     *
73
     * @param array                   $resolverArgs
74
     * @param ValidatorInterface|null $validator
75
     * @param ValidatorFactory        $factory
76
     * @param array $mapping
77
     */
78 1
    public function __construct(array $resolverArgs, ?ValidatorInterface $validator, ValidatorFactory $factory, array $mapping)
79
    {
80 1
        if (null === $validator) {
81
            throw new ServiceNotFoundException(
82
                "The 'validator' service is not found. To use the 'InputValidator' you need to install the 
83
                Symfony Validator Component first. See: 'https://symfony.com/doc/current/validation.html'"
84
            );
85
        }
86
87 1
        $this->resolverArgs      = $this->mapResolverArgs($resolverArgs);
88 1
        $this->info              = $this->resolverArgs['info'];
89 1
        $this->constraintMapping = $mapping;
90 1
        $this->validator 	     = $validator;
91 1
        $this->validatorFactory  = $factory;
92 1
        $this->metadataFactory   = new MetadataFactory();
93 1
    }
94
95
    /**
96
     * Converts a numeric array of resolver args to an associative one.
97
     *
98
     * @param array  $rawReolverArgs
99
     * @return array
100
     */
101 1
    private function mapResolverArgs(array $rawReolverArgs)
102
    {
103
        return [
104 1
            'value'   => $rawReolverArgs[0],
105 1
            'args'    => $rawReolverArgs[1],
106 1
            'context' => $rawReolverArgs[2],
107 1
            'info'    => $rawReolverArgs[3],
108
        ];
109
    }
110
111
    /**
112
     * @param string|array|null $groups
113
     * @param bool              $throw
114
     *
115
     * @return ConstraintViolationListInterface|null
116
     * @throws ArgumentsValidationException
117
     */
118 1
    public function validate($groups = null, bool $throw = true): ?ConstraintViolationListInterface
119
    {
120 1
        $rootObject = new ValidationNode($this->info->parentType, $this->info->fieldName, null, $this->resolverArgs);
121
122 1
        $this->buildValidationTree($rootObject, $this->constraintMapping, $this->resolverArgs['args']->getArrayCopy());
123
124 1
        $validator = $this->validatorFactory->createValidator($this->metadataFactory);
125
126 1
        $errors = $validator->validate($rootObject, null, $groups);
127
128 1
        if ($throw && $errors->count() > 0) {
129
            throw new ArgumentsValidationException($errors);
130
        } else {
131 1
            return $errors;
132
        }
133
    }
134
135
    /**
136
     * Creates a composition of ValidationNode objects from args
137
     * and simultaneously applies to them validation constraints.
138
     *
139
     * @param ValidationNode    $rootObject
140
     * @param array             $constraintMapping
141
     * @param array             $args
142
     * @return ValidationNode
143
     */
144 1
    protected function buildValidationTree(ValidationNode $rootObject, array $constraintMapping, array $args): ValidationNode
145
    {
146 1
        $metadata = new ObjectMetadata($rootObject);
147
148 1
        $this->applyClassConstraints($metadata, $constraintMapping['class']);
149
150 1
        foreach ($constraintMapping['properties'] as $property => $params) {
151
152 1
            if (!empty($params['cascade']) && isset($args[$property])) {
153 1
                $options = $params['cascade'];
154 1
                $type = $this->info->schema->getType($options['referenceType']);
155
156 1
                if ($options['isCollection']) {
157
                    $rootObject->$property = $this->createCollectionNode($args[$property], $type, $rootObject);
158
                } else {
159 1
                    $rootObject->$property = $this->createObjectNode($args[$property], $type, $rootObject);
160
                }
161
162 1
                $valid = new Valid();
163
164 1
                if (!empty($options['groups'])) {
165 1
                    $valid->groups = $options['groups'];
166
                }
167
168 1
                $metadata->addPropertyConstraint($property, $valid);
169
            } else {
170 1
                $rootObject->$property = $args[$property] ?? null;
171
            }
172
173 1
            foreach ($params ?? [] as $key => $value) {
174 1
                if (null === $value) continue;
175
176 1
                switch ($key) {
177 1
                    case 'link':
178
                        [$FQCN, $property, $type] = $value;
179
180
                        if (!in_array($FQCN, $this->cachedMetadata)) {
181
                            $this->cachedMetadata[$FQCN] = $this->validator->getMetadataFor($FQCN);
182
                        }
183
184
                        // Get metadata from the property and it's getters
185
                        $propertyMetadata = $this->cachedMetadata[$FQCN]->getPropertyMetadata($property);
186
187
                        foreach ($propertyMetadata as $memberMetadata) {
188
                            // Allow only constraints specified by the "link" matcher
189
                            if (self::TYPE_GETTER === $type) {
190
                                if (!$memberMetadata instanceof GetterMetadata) continue;
191
                            } else if (self::TYPE_PROPERTY === $type) {
192
                                if (!$memberMetadata instanceof PropertyMetadata) continue;
193
                            }
194
195
                            $metadata->addPropertyConstraints($property, $memberMetadata->getConstraints());
196
                        }
197
198
                        break;
199 1
                    case 'constraints':
200 1
                        $metadata->addPropertyConstraints($property, $value);
201 1
                        break;
202 1
                    case 'cascade':
203 1
                        break;
204
                    default:
205
                        throw new RuntimeException("Validation entry '$key' doesn't exist");
206
                }
207
            }
208
        }
209
210 1
        $this->metadataFactory->addMetadata($metadata);
211
212 1
        return $rootObject;
213
    }
214
215
    /**
216
     * @param array                           $values
217
     * @param ObjectType|InputObjectType|Type $type
218
     * @param ValidationNode $parent
219
     * @return array
220
     */
221
    private function createCollectionNode(array $values, Type $type, ValidationNode $parent): array
222
    {
223
        $collection = [];
224
225
        foreach ($values as $value) {
226
            $collection[] = $this->createObjectNode($value, $type, $parent);
227
        }
228
229
        return $collection;
230
    }
231
232
    /**
233
     * @param array                           $value
234
     * @param ObjectType|InputObjectType|Type $type
235
     * @param $parent
236
     * @return ValidationNode
237
     */
238 1
    private function createObjectNode(array $value, Type $type, ValidationNode $parent): ValidationNode
239
    {
240
        $mapping = [
241 1
            'class' => $type->config['validation'] ?? null,
242
        ];
243
244 1
        foreach ($type->getFields() as $fieldName => $inputField) {
0 ignored issues
show
Bug introduced by
The method getFields() does not exist on GraphQL\Type\Definition\Type. It seems like you code against a sub-type of GraphQL\Type\Definition\Type such as GraphQL\Type\Definition\InterfaceType or GraphQL\Type\Definition\ObjectType or GraphQL\Type\Definition\InputObjectType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

244
        foreach ($type->/** @scrutinizer ignore-call */ getFields() as $fieldName => $inputField) {
Loading history...
245 1
            $mapping['properties'][$fieldName] = $inputField->config['validation'];
246
        }
247
248 1
        return $this->buildValidationTree(new ValidationNode($type, null, $parent, $this->resolverArgs), $mapping, $value);
249
    }
250
251
    /**
252
     * @param ObjectMetadata $metadata
253
     * @param array          $constraints
254
     */
255 1
    private function applyClassConstraints(ObjectMetadata $metadata, ?array $constraints)
256
    {
257 1
        foreach ($constraints ?? [] as $key => $value) {
258
            if (null === $value) continue;
259
260
            switch ($key) {
261
                case 'link':
262
                    $linkedMetadata = $this->validator->getMetadataFor($value);
263
                    $metadata->addConstraints($linkedMetadata->getConstraints());
264
                    break;
265
                case 'constraints':
266
                    foreach ($value as $constraint) {
267
                        if ($constraint instanceof Constraint) {
268
                            $metadata->addConstraint($constraint);
269
                        } else if ($constraint instanceof GroupSequence) {
270
                            $metadata->setGroupSequence($constraint);
271
                        }
272
                    }
273
                    break;
274
            }
275
        }
276 1
    }
277
}
278