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
06:10
created

InputValidator   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Test Coverage

Coverage 99.03%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 38
eloc 112
c 1
b 0
f 0
dl 0
loc 248
ccs 102
cts 103
cp 0.9903
rs 9.36

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 2
A mapResolverArgs() 0 7 1
A createCollectionNode() 0 9 2
A validate() 0 19 3
A createObjectNode() 0 14 2
B applyClassConstraints() 0 19 7
D buildValidationTree() 0 78 18
A restructureShortForm() 0 4 2
A __invoke() 0 3 1
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
     *
89
     * @throws ArgumentsValidationException
90
     */
91 16
    public function validate($groups = null, bool $throw = true): ?ConstraintViolationListInterface
92
    {
93 16
        $rootObject = new ValidationNode($this->info->parentType, $this->info->fieldName, null, $this->resolverArgs);
94
95 16
        $this->buildValidationTree(
96 16
            $rootObject,
97 16
            $this->propertiesMapping,
98 16
            $this->classMapping,
99 16
            $this->resolverArgs['args']->getArrayCopy()
100
        );
101
102 16
        $validator = $this->validatorFactory->createValidator($this->metadataFactory);
103
104 16
        $errors = $validator->validate($rootObject, null, $groups);
105
106 16
        if ($throw && $errors->count() > 0) {
107 6
            throw new ArgumentsValidationException($errors);
108
        } else {
109 10
            return $errors;
110
        }
111
    }
112
113
    /**
114
     * Creates a composition of ValidationNode objects from args
115
     * and simultaneously applies to them validation constraints.
116
     */
117 16
    protected function buildValidationTree(ValidationNode $rootObject, array $propertiesMapping, array $classMapping, array $args): ValidationNode
118
    {
119 16
        $metadata = new ObjectMetadata($rootObject);
120
121 16
        if (!empty($classMapping)) {
122 8
            $this->applyClassConstraints($metadata, $classMapping);
123
        }
124
125 16
        foreach ($propertiesMapping as $property => $params) {
126 16
            if (!empty($params['cascade']) && isset($args[$property])) {
127 6
                $options = $params['cascade'];
128
129
                /** @var ObjectType|InputObjectType $type */
130 6
                $type = $options['referenceType'];
131
132 6
                if (null === $type) {
133
                    continue;
134
                }
135
136 6
                if ($options['isCollection']) {
137 2
                    $rootObject->$property = $this->createCollectionNode($args[$property], $type, $rootObject);
138
                } else {
139 6
                    $rootObject->$property = $this->createObjectNode($args[$property], $type, $rootObject);
140
                }
141
142 6
                $valid = new Valid();
143
144 6
                if (!empty($options['groups'])) {
145 4
                    $valid->groups = $options['groups'];
146
                }
147
148 6
                $metadata->addPropertyConstraint($property, $valid);
149
            } else {
150 16
                $rootObject->$property = $args[$property] ?? null;
151
            }
152
153 16
            $this->restructureShortForm($params);
154
155 16
            foreach ($params ?? [] as $key => $value) {
156
                switch ($key) {
157 16
                    case 'link':
158 2
                        [$fqcn, $property, $type] = $value;
159
160 2
                        if (!\in_array($fqcn, $this->cachedMetadata)) {
161 2
                            $this->cachedMetadata[$fqcn] = $this->validator->getMetadataFor($fqcn);
162
                        }
163
164
                        // Get metadata from the property and it's getters
165 2
                        $propertyMetadata = $this->cachedMetadata[$fqcn]->getPropertyMetadata($property);
166
167 2
                        foreach ($propertyMetadata as $memberMetadata) {
168
                            // Allow only constraints specified by the "link" matcher
169 2
                            if (self::TYPE_GETTER === $type) {
170 2
                                if (!$memberMetadata instanceof GetterMetadata) {
171 2
                                    continue;
172
                                }
173 2
                            } elseif (self::TYPE_PROPERTY === $type) {
174 2
                                if (!$memberMetadata instanceof PropertyMetadata) {
175 2
                                    continue;
176
                                }
177
                            }
178
179 2
                            $metadata->addPropertyConstraints($property, $memberMetadata->getConstraints());
180
                        }
181
182 2
                        break;
183 14
                    case 'constraints':
184 14
                        $metadata->addPropertyConstraints($property, $value);
185 14
                        break;
186 6
                    case 'cascade':
187 6
                        break;
188
                }
189
            }
190
        }
191
192 16
        $this->metadataFactory->addMetadata($metadata);
193
194 16
        return $rootObject;
195
    }
196
197
    /**
198
     * @param ObjectType|InputObjectType $type
199
     */
200 2
    private function createCollectionNode(array $values, $type, ValidationNode $parent): array
201
    {
202 2
        $collection = [];
203
204 2
        foreach ($values as $value) {
205 2
            $collection[] = $this->createObjectNode($value, $type, $parent);
206
        }
207
208 2
        return $collection;
209
    }
210
211
    /**
212
     * @param ObjectType|InputObjectType $type
213
     */
214 6
    private function createObjectNode(array $value, $type, ValidationNode $parent): ValidationNode
215
    {
216 6
        $classMapping = $type->config['validation'] ?? [];
217 6
        $propertiesMapping = [];
218
219 6
        foreach ($type->getFields() as $fieldName => $inputField) {
220 6
            $propertiesMapping[$fieldName] = $inputField->config['validation'];
221
        }
222
223 6
        return $this->buildValidationTree(
224 6
            new ValidationNode($type, null, $parent, $this->resolverArgs),
225
            $propertiesMapping,
226
            $classMapping,
227
            $value
228
        );
229
    }
230
231 8
    private function applyClassConstraints(ObjectMetadata $metadata, array $rules): void
232
    {
233 8
        $this->restructureShortForm($rules);
234
235 8
        foreach ($rules as $key => $value) {
236
            switch ($key) {
237 8
                case 'link':
238 2
                    $linkedMetadata = $this->validator->getMetadataFor($value);
239 2
                    $metadata->addConstraints($linkedMetadata->getConstraints());
240 2
                    break;
241 8
                case 'constraints':
242 8
                    foreach ($value as $constraint) {
243 8
                        if ($constraint instanceof Constraint) {
244 2
                            $metadata->addConstraint($constraint);
245 6
                        } elseif ($constraint instanceof GroupSequence) {
246 6
                            $metadata->setGroupSequence($constraint);
247
                        }
248
                    }
249 8
                    break;
250
            }
251
        }
252 8
    }
253
254
    /**
255
     * @param array $rules
256
     */
257 16
    private function restructureShortForm(array &$rules): void
258
    {
259 16
        if (isset($rules[0])) {
260 14
            $rules = ['constraints' => $rules];
261
        }
262 16
    }
263
264
    /**
265
     * @param string|array|null $groups
266
     *
267
     * @throws ArgumentsValidationException
268
     */
269 10
    public function __invoke($groups = null, bool $throw = true): ?ConstraintViolationListInterface
270
    {
271 10
        return $this->validate($groups, $throw);
272
    }
273
}
274