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 Timur
06:41
created

InputValidator   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 37
eloc 110
c 2
b 1
f 0
dl 0
loc 244
ccs 101
cts 101
cp 1
rs 9.44

9 Methods

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