• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 10885997573

16 Sep 2024 02:31PM UTC coverage: 6.961% (-0.3%) from 7.281%
10885997573

push

github

web-flow
 feat(laravel): filter validations rules

* refactor(metadata): move parameter validation to the validator component

* feat(laravel): validations rules filters

* cs fixes

* fix(laravel): eloquent filters validation

* fix(laravel): eloquent filters

* fixes

* fix

---------

Co-authored-by: soyuka <soyuka@users.noreply.github.com>
Co-authored-by: Nathan <nathan@les-tilleuls.coop>

87 of 188 new or added lines in 6 files covered. (46.28%)

7 existing lines in 2 files now uncovered.

11475 of 164843 relevant lines covered (6.96%)

14.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.81
/src/Validator/Metadata/Resource/Factory/ParameterValidationResourceMetadataCollectionFactory.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Validator\Metadata\Resource\Factory;
15

16
use ApiPlatform\Metadata\HttpOperation;
17
use ApiPlatform\Metadata\Parameter;
18
use ApiPlatform\Metadata\Parameters;
19
use ApiPlatform\Metadata\QueryParameter;
20
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
21
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
22
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
23
use Psr\Container\ContainerInterface;
24
use Symfony\Component\Validator\Constraints\Choice;
25
use Symfony\Component\Validator\Constraints\Count;
26
use Symfony\Component\Validator\Constraints\DivisibleBy;
27
use Symfony\Component\Validator\Constraints\GreaterThan;
28
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
29
use Symfony\Component\Validator\Constraints\Length;
30
use Symfony\Component\Validator\Constraints\LessThan;
31
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
32
use Symfony\Component\Validator\Constraints\NotBlank;
33
use Symfony\Component\Validator\Constraints\NotNull;
34
use Symfony\Component\Validator\Constraints\Regex;
35
use Symfony\Component\Validator\Constraints\Type;
36
use Symfony\Component\Validator\Constraints\Unique;
37

38
final class ParameterValidationResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
39
{
40
    public function __construct(
41
        private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
42
        private readonly ?ContainerInterface $filterLocator = null,
43
    ) {
44
    }
2,255✔
45

46
    public function create(string $resourceClass): ResourceMetadataCollection
47
    {
48
        $resourceMetadataCollection = $this->decorated?->create($resourceClass) ?? new ResourceMetadataCollection($resourceClass);
42✔
49

50
        foreach ($resourceMetadataCollection as $i => $resource) {
42✔
51
            $operations = $resource->getOperations();
34✔
52

53
            foreach ($operations as $operationName => $operation) {
34✔
54
                $parameters = $operation->getParameters() ?? new Parameters();
34✔
55
                foreach ($parameters as $key => $parameter) {
34✔
56
                    $parameters->add($key, $this->addSchemaValidation($parameter));
3✔
57
                }
58

59
                // As we deprecate the parameter validator, we declare a parameter for each filter transfering validation to the new system
60
                if ($operation->getFilters() && 0 === $parameters->count()) {
34✔
61
                    $parameters = $this->addFilterValidation($operation);
15✔
62
                }
63

64
                if (\count($parameters) > 0) {
34✔
65
                    $operations->add($operationName, $operation->withParameters($parameters));
13✔
66
                }
67
            }
68

69
            $resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());
34✔
70

71
            if (!$graphQlOperations = $resource->getGraphQlOperations()) {
34✔
72
                continue;
16✔
73
            }
74

75
            foreach ($graphQlOperations as $operationName => $operation) {
31✔
76
                $parameters = $operation->getParameters() ?? new Parameters();
31✔
77
                foreach ($operation->getParameters() ?? [] as $key => $parameter) {
31✔
78
                    $parameters->add($key, $this->addSchemaValidation($parameter));
3✔
79
                }
80

81
                if (\count($parameters) > 0) {
31✔
82
                    $graphQlOperations[$operationName] = $operation->withParameters($parameters);
3✔
83
                }
84
            }
85

86
            $resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
31✔
87
        }
88

89
        return $resourceMetadataCollection;
42✔
90
    }
91

92
    private function addSchemaValidation(Parameter $parameter, ?array $schema = null, ?bool $required = null, ?OpenApiParameter $openApi = null): Parameter
93
    {
94
        $schema ??= $parameter->getSchema();
13✔
95
        $required ??= $parameter->getRequired() ?? false;
13✔
96
        $openApi ??= $parameter->getOpenApi();
13✔
97

98
        // When it's an array of openapi parameters take the first one as it's probably just a variant of the query parameter,
99
        // only getAllowEmptyValue is used here anyways
100
        if (\is_array($openApi)) {
13✔
NEW
101
            $openApi = $openApi[0];
×
102
        } elseif (false === $openApi) {
13✔
103
            $openApi = null;
10✔
104
        }
105

106
        $assertions = [];
13✔
107

108
        if ($required && false !== ($allowEmptyValue = $openApi?->getAllowEmptyValue())) {
13✔
109
            $assertions[] = new NotNull(message: \sprintf('The parameter "%s" is required.', $parameter->getKey()));
4✔
110
        }
111

112
        if (false === ($allowEmptyValue ?? $openApi?->getAllowEmptyValue())) {
13✔
113
            $assertions[] = new NotBlank(allowNull: !$required);
8✔
114
        }
115

116
        if (isset($schema['exclusiveMinimum'])) {
13✔
117
            $assertions[] = new GreaterThan(value: $schema['exclusiveMinimum']);
4✔
118
        }
119

120
        if (isset($schema['exclusiveMaximum'])) {
13✔
121
            $assertions[] = new LessThan(value: $schema['exclusiveMaximum']);
4✔
122
        }
123

124
        if (isset($schema['minimum'])) {
13✔
125
            $assertions[] = new GreaterThanOrEqual(value: $schema['minimum']);
4✔
126
        }
127

128
        if (isset($schema['maximum'])) {
13✔
129
            $assertions[] = new LessThanOrEqual(value: $schema['maximum']);
4✔
130
        }
131

132
        if (isset($schema['pattern'])) {
13✔
133
            $assertions[] = new Regex($schema['pattern']);
4✔
134
        }
135

136
        if (isset($schema['maxLength']) || isset($schema['minLength'])) {
13✔
137
            $assertions[] = new Length(min: $schema['minLength'] ?? null, max: $schema['maxLength'] ?? null);
4✔
138
        }
139

140
        if (isset($schema['minItems']) || isset($schema['maxItems'])) {
13✔
141
            $assertions[] = new Count(min: $schema['minItems'] ?? null, max: $schema['maxItems'] ?? null);
4✔
142
        }
143

144
        if (isset($schema['multipleOf'])) {
13✔
145
            $assertions[] = new DivisibleBy(value: $schema['multipleOf']);
4✔
146
        }
147

148
        if ($schema['uniqueItems'] ?? false) {
13✔
149
            $assertions[] = new Unique();
4✔
150
        }
151

152
        if (isset($schema['enum'])) {
13✔
153
            $assertions[] = new Choice(choices: $schema['enum']);
6✔
154
        }
155

156
        if (isset($schema['type']) && 'array' === $schema['type']) {
13✔
157
            $assertions[] = new Type(type: 'array');
3✔
158
        }
159

160
        if (!$assertions) {
13✔
161
            return $parameter;
10✔
162
        }
163

164
        if (1 === \count($assertions)) {
9✔
165
            return $parameter->withConstraints($assertions[0]);
9✔
166
        }
167

168
        return $parameter->withConstraints($assertions);
3✔
169
    }
170

171
    private function addFilterValidation(HttpOperation $operation): Parameters
172
    {
173
        $parameters = new Parameters();
15✔
174
        $internalPriority = -1;
15✔
175

176
        foreach ($operation->getFilters() as $filter) {
15✔
177
            if (!$this->filterLocator->has($filter)) {
15✔
178
                continue;
2✔
179
            }
180

181
            $filter = $this->filterLocator->get($filter);
15✔
182
            foreach ($filter->getDescription($operation->getClass()) as $parameterName => $definition) {
15✔
183
                $key = $parameterName;
13✔
184
                $required = $definition['required'] ?? false;
13✔
185
                $schema = $definition['schema'] ?? null;
13✔
186

187
                $openApi = null;
13✔
188
                if (isset($definition['openapi']) && $definition['openapi'] instanceof OpenApiParameter) {
13✔
189
                    $openApi = $definition['openapi'];
7✔
190
                }
191

192
                // The query parameter validator forced this, lets maintain BC on filters
193
                if (true === $required && !$openApi) {
13✔
194
                    $openApi = new OpenApiParameter(name: $key, in: 'query', allowEmptyValue: false);
4✔
195
                }
196

197
                $parameters->add($key, $this->addSchemaValidation(
13✔
198
                    // we disable openapi and hydra on purpose as their docs comes from filters see the condition for addFilterValidation above
199
                    new QueryParameter(key: $key, property: $definition['property'] ?? null, priority: $internalPriority--, schema: $schema, openApi: false, hydra: false),
13✔
200
                    $schema,
13✔
201
                    $required,
13✔
202
                    $openApi
13✔
203
                ));
13✔
204
            }
205
        }
206

207
        return $parameters;
15✔
208
    }
209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc