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

InputValidator::buildValidationTree()   C

Complexity

Conditions 17
Paths 86

Size

Total Lines 67
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 38
dl 0
loc 67
rs 5.2166
c 0
b 0
f 0
cc 17
nc 86
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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