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 (#685)
by
unknown
14:33 queued 11:42
created

InputValidator::__invoke()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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