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
24:20
created

ArgumentsValidator::buildValidationTree()   C

Complexity

Conditions 17
Paths 86

Size

Total Lines 69
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 40
dl 0
loc 69
rs 5.2166
c 1
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 RuntimeException;
15
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
16
use Symfony\Component\Validator\Constraint;
17
use Symfony\Component\Validator\Constraints\GroupSequence;
18
use Symfony\Component\Validator\Constraints\Valid;
19
use Symfony\Component\Validator\ConstraintViolationListInterface;
20
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
21
use Symfony\Component\Validator\Mapping\GetterMetadata;
22
use Symfony\Component\Validator\Mapping\PropertyMetadata;
23
use Symfony\Component\Validator\Validator\ValidatorInterface;
24
25
/**
26
 * ArgumentsValidator
27
 *
28
 * @author Timur Murtukov <[email protected]>
29
 */
30
class ArgumentsValidator
31
{
32
    private const TYPE_PROPERTY = 'property';
33
    private const TYPE_GETTER   = 'getter';
34
35
    /**
36
     * @var array
37
     */
38
    private $resolverArgs;
39
40
    /**
41
     * @var array
42
     */
43
    private $constraintMapping;
44
45
    /**
46
     * @var ValidatorInterface
47
     */
48
    private $validator;
49
50
    /**
51
     * @var MetadataFactory
52
     */
53
    private $metadataFactory;
54
55
    /**
56
     * @var ResolveInfo
57
     */
58
    private $info;
59
60
    /**
61
     * @var ValidatorFactory
62
     */
63
    private $validatorFactory;
64
65
    /**
66
     * @var ClassMetadataInterface[]
67
     */
68
    private $cachedMetadata = [];
69
70
    /**
71
     * ArgumentsValidator constructor.
72
     *
73
     * @param array                   $resolverArgs
74
     * @param ValidatorInterface|null $validator
75
     * @param ValidatorFactory        $factory
76
     * @param array $mapping
77
     */
78
    public function __construct(array $resolverArgs, ?ValidatorInterface $validator, ValidatorFactory $factory, array $mapping)
79
    {
80
        if (null === $validator) {
81
            throw new ServiceNotFoundException(
82
                "The 'validator' service is not found. To use the 'ArgumentsValidator' you need to install
83
                the Validator Component first. See: 'https://symfony.com/doc/current/validation.html'"
84
            );
85
        }
86
87
        $this->resolverArgs      = $this->mapResolverArgs($resolverArgs);
88
        $this->info              = $this->resolverArgs['info'];
89
        $this->constraintMapping = $mapping;
90
        $this->validator 	     = $validator;
91
        $this->validatorFactory  = $factory;
92
        $this->metadataFactory   = new MetadataFactory();
93
    }
94
95
    /**
96
     * Converts a numeric array of resolver args to an associative one.
97
     *
98
     * @param array  $rawReolverArgs
99
     * @return array
100
     */
101
    private function mapResolverArgs(array $rawReolverArgs)
102
    {
103
        return [
104
            'value'   => $rawReolverArgs[0],
105
            'args'    => $rawReolverArgs[1],
106
            'context' => $rawReolverArgs[2],
107
            'info'    => $rawReolverArgs[3],
108
        ];
109
    }
110
111
    /**
112
     * @param string|array|null $groups
113
     * @param bool              $throw
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
                    default:
204
                        throw new RuntimeException("Validation entry '$key' doesn't exist");
205
                }
206
            }
207
        }
208
209
        $this->metadataFactory->addMetadata($metadata);
210
211
        return $rootObject;
212
    }
213
214
    /**
215
     * @param array                           $values
216
     * @param ObjectType|InputObjectType|Type $type
217
     * @param ValidationNode $parent
218
     * @return array
219
     */
220
    private function createCollectionNode(array $values, Type $type, ValidationNode $parent): array
221
    {
222
        $collection = [];
223
224
        foreach ($values as $value) {
225
            $collection[] = $this->createObjectNode($value, $type, $parent);
226
        }
227
228
        return $collection;
229
    }
230
231
    /**
232
     * @param array                           $value
233
     * @param ObjectType|InputObjectType|Type $type
234
     * @param $parent
235
     * @return ValidationNode
236
     */
237
    private function createObjectNode(array $value, Type $type, ValidationNode $parent): ValidationNode
238
    {
239
        $mapping = [
240
            'class' => $type->config['validation'] ?? null,
241
        ];
242
243
        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

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