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 (#536)
by
unknown
20:43
created

InputValidator::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

246
        foreach ($type->/** @scrutinizer ignore-call */ getFields() as $fieldName => $inputField) {
Loading history...
247
            $mapping['properties'][$fieldName] = $inputField->config['validation'];
248
        }
249
250
        return $this->buildValidationTree(new ValidationNode($type, null, $parent, $this->resolverArgs), $mapping, $value);
251
    }
252
253
    /**
254
     * @param ObjectMetadata $metadata
255
     * @param array          $constraints
256
     */
257
    private function applyClassConstraints(ObjectMetadata $metadata, ?array $constraints): void
258
    {
259
        foreach ($constraints ?? [] as $key => $value) {
260
            if (null === $value) {
261
                continue;
262
            }
263
264
            switch ($key) {
265
                case 'link':
266
                    $linkedMetadata = $this->validator->getMetadataFor($value);
267
                    $metadata->addConstraints($linkedMetadata->getConstraints());
268
                    break;
269
                case 'constraints':
270
                    foreach ($value as $constraint) {
271
                        if ($constraint instanceof Constraint) {
272
                            $metadata->addConstraint($constraint);
273
                        } elseif ($constraint instanceof GroupSequence) {
274
                            $metadata->setGroupSequence($constraint);
275
                        }
276
                    }
277
                    break;
278
            }
279
        }
280
    }
281
}
282