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

InputValidator::applyClassConstraints()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 8.0155

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
dl 0
loc 23
ccs 15
cts 16
cp 0.9375
rs 8.4444
c 1
b 0
f 0
cc 8
nc 8
nop 2
crap 8.0155
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 16
                if (null === $value) {
153
                    continue;
154
                }
155
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 8
            if (null === $value) {
237
                continue;
238
            }
239
240
            switch ($key) {
241 8
                case 'link':
242 2
                    $linkedMetadata = $this->validator->getMetadataFor($value);
243 2
                    $metadata->addConstraints($linkedMetadata->getConstraints());
244 2
                    break;
245 8
                case 'constraints':
246 8
                    foreach ($value as $constraint) {
247 8
                        if ($constraint instanceof Constraint) {
248 2
                            $metadata->addConstraint($constraint);
249 6
                        } elseif ($constraint instanceof GroupSequence) {
250 6
                            $metadata->setGroupSequence($constraint);
251
                        }
252
                    }
253 8
                    break;
254
            }
255
        }
256 8
    }
257
258
    /**
259
     * @param array $rules
260
     */
261 16
    private function restructureShortForm(array &$rules): void
262
    {
263 16
        if (isset($rules[0])) {
264 14
            $rules = ['constraints' => $rules];
265
        }
266 16
    }
267
268
    /**
269
     * @param string|array|null $groups
270
     *
271
     * @throws ArgumentsValidationException
272
     */
273
    public function __invoke($groups = null, bool $throw = true): ?ConstraintViolationListInterface
274
    {
275
        return $this->validate($groups, $throw);
276
    }
277
}
278