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 (#682)
by
unknown
21:39
created

InputValidator::buildValidationTree()   D

Complexity

Conditions 18
Paths 172

Size

Total Lines 76
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 18

Importance

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

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

213
        foreach ($type->/** @scrutinizer ignore-call */ getFields() as $fieldName => $inputField) {
Loading history...
214
            $propertiesMapping[$fieldName] = $inputField->config['validation'];
215
        }
216
217
        return $this->buildValidationTree(
218
            new ValidationNode($type, null, $parent, $this->resolverArgs),
219
            $propertiesMapping,
220
            $classMapping,
221
            $value
222 2
        );
223
    }
224 2
225
    private function applyClassConstraints(ObjectMetadata $metadata, array $rules): void
226 2
    {
227 2
        $this->restructureShortForm($rules);
228
229
        foreach ($rules as $key => $value) {
230 2
            if (null === $value) {
231
                continue;
232
            }
233
234
            switch ($key) {
235
                case 'link':
236
                    $linkedMetadata = $this->validator->getMetadataFor($value);
237
                    $metadata->addConstraints($linkedMetadata->getConstraints());
238
                    break;
239
                case 'constraints':
240 6
                    foreach ($value as $constraint) {
241
                        if ($constraint instanceof Constraint) {
242
                            $metadata->addConstraint($constraint);
243 6
                        } elseif ($constraint instanceof GroupSequence) {
244
                            $metadata->setGroupSequence($constraint);
245
                        }
246 6
                    }
247 6
                    break;
248
            }
249
        }
250 6
    }
251
252
    /**
253
     * @param array $rules
254
     */
255
    private function restructureShortForm(array &$rules): void
256
    {
257 17
        if (isset($rules[0])) {
258
            $rules = ['constraints' => $rules];
259 17
        }
260 8
    }
261 8
262
    /**
263
     * @param string|array|null $groups
264 8
     *
265 8
     * @throws ArgumentsValidationException
266 2
     */
267 2
    public function __invoke($groups = null, bool $throw = true): ?ConstraintViolationListInterface
268 2
    {
269 8
        return $this->validate($groups, $throw);
270 8
    }
271
}
272