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

Passed
Pull Request — master (#682)
by
unknown
22:32
created

InputValidator::validate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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