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 (#684)
by
unknown
06:07
created

InputValidator::buildValidationTree()   D

Complexity

Conditions 18
Paths 172

Size

Total Lines 76
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 18.0047

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 43
dl 0
loc 76
ccs 40
cts 41
cp 0.9756
rs 4.2666
c 1
b 0
f 0
cc 18
nc 172
nop 4
crap 18.0047

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