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 (#685)
by
unknown
17:49
created

InputValidator   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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

8 Methods

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