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

api-platform / core / 10749392760

07 Sep 2024 06:39AM UTC coverage: 7.646%. Remained the same
10749392760

push

github

soyuka
test: remove hydra prefix from guides

0 of 21 new or added lines in 6 files covered. (0.0%)

6834 existing lines in 225 files now uncovered.

12538 of 163980 relevant lines covered (7.65%)

22.78 hits per line

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

93.53
/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.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\Metadata\Resource\Factory;
15

16
use ApiPlatform\Metadata\FilterInterface;
17
use ApiPlatform\Metadata\HasOpenApiParameterFilterInterface;
18
use ApiPlatform\Metadata\HasSchemaFilterInterface;
19
use ApiPlatform\Metadata\HttpOperation;
20
use ApiPlatform\Metadata\Parameter;
21
use ApiPlatform\Metadata\Parameters;
22
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
23
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
24
use ApiPlatform\Metadata\QueryParameter;
25
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
26
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
27
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
28
use Psr\Container\ContainerInterface;
29
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
30
use Symfony\Component\Validator\Constraints\Choice;
31
use Symfony\Component\Validator\Constraints\Count;
32
use Symfony\Component\Validator\Constraints\DivisibleBy;
33
use Symfony\Component\Validator\Constraints\GreaterThan;
34
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
35
use Symfony\Component\Validator\Constraints\Length;
36
use Symfony\Component\Validator\Constraints\LessThan;
37
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
38
use Symfony\Component\Validator\Constraints\NotBlank;
39
use Symfony\Component\Validator\Constraints\NotNull;
40
use Symfony\Component\Validator\Constraints\Regex;
41
use Symfony\Component\Validator\Constraints\Type;
42
use Symfony\Component\Validator\Constraints\Unique;
43
use Symfony\Component\Validator\Validator\ValidatorInterface;
44

45
/**
46
 * Prepares Parameters documentation by reading its filter details and declaring an OpenApi parameter.
47
 *
48
 * @experimental
49
 */
50
final class ParameterResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
51
{
52
    public function __construct(
53
        private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
54
        private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory,
55
        private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
56
        private readonly ?ContainerInterface $filterLocator = null,
57
        private readonly ?NameConverterInterface $nameConverter = null,
58
    ) {
UNCOV
59
    }
2,251✔
60

61
    public function create(string $resourceClass): ResourceMetadataCollection
62
    {
UNCOV
63
        $resourceMetadataCollection = $this->decorated?->create($resourceClass) ?? new ResourceMetadataCollection($resourceClass);
42✔
64

UNCOV
65
        $propertyNames = [];
42✔
UNCOV
66
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $i => $property) {
42✔
UNCOV
67
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
42✔
UNCOV
68
            if ($propertyMetadata->isReadable()) {
42✔
UNCOV
69
                $propertyNames[] = $property;
42✔
70
            }
71
        }
72

UNCOV
73
        $properties = array_flip($propertyNames);
42✔
UNCOV
74
        foreach ($resourceMetadataCollection as $i => $resource) {
42✔
UNCOV
75
            $operations = $resource->getOperations();
34✔
76

UNCOV
77
            $internalPriority = -1;
34✔
UNCOV
78
            foreach ($operations as $operationName => $operation) {
34✔
UNCOV
79
                $parameters = $operation->getParameters() ?? new Parameters();
34✔
UNCOV
80
                foreach ($parameters as $key => $parameter) {
34✔
UNCOV
81
                    if (':property' === $key) {
3✔
UNCOV
82
                        foreach ($propertyNames as $property) {
3✔
UNCOV
83
                            $converted = $this->nameConverter?->denormalize($property) ?? $property;
3✔
UNCOV
84
                            $propertyParameter = $this->setDefaults($converted, $parameter, $resourceClass, $properties);
3✔
UNCOV
85
                            $priority = $propertyParameter->getPriority() ?? $internalPriority--;
3✔
UNCOV
86
                            $parameters->add($converted, $this->addFilterMetadata($propertyParameter->withPriority($priority)->withKey($converted)));
3✔
87
                        }
88

UNCOV
89
                        $parameters->remove($key, $parameter::class);
3✔
UNCOV
90
                        continue;
3✔
91
                    }
92

UNCOV
93
                    $key = $parameter->getKey() ?? $key;
3✔
94

UNCOV
95
                    if (str_contains($key, ':property')) {
3✔
UNCOV
96
                        $p = [];
3✔
UNCOV
97
                        foreach ($propertyNames as $prop) {
3✔
UNCOV
98
                            $p[$this->nameConverter?->denormalize($prop) ?? $prop] = $prop;
3✔
99
                        }
100

UNCOV
101
                        $parameter = $parameter->withExtraProperties(($parameter->getExtraProperties() ?? []) + ['_properties' => $p]);
3✔
102
                    }
103

UNCOV
104
                    $parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties);
3✔
UNCOV
105
                    $priority = $parameter->getPriority() ?? $internalPriority--;
3✔
UNCOV
106
                    $parameters->add($key, $this->addFilterMetadata($parameter->withPriority($priority)));
3✔
107
                }
108

109
                // As we deprecate the parameter validator, we declare a parameter for each filter transfering validation to the new system
UNCOV
110
                if ($operation->getFilters() && 0 === $parameters->count()) {
34✔
UNCOV
111
                    $parameters = $this->addFilterValidation($operation);
15✔
112
                }
113

UNCOV
114
                if (\count($parameters) > 0) {
34✔
UNCOV
115
                    $operations->add($operationName, $operation->withParameters($parameters));
13✔
116
                }
117
            }
118

UNCOV
119
            $resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());
34✔
120

UNCOV
121
            if (!$graphQlOperations = $resource->getGraphQlOperations()) {
34✔
UNCOV
122
                continue;
16✔
123
            }
124

UNCOV
125
            $internalPriority = -1;
31✔
UNCOV
126
            foreach ($graphQlOperations as $operationName => $operation) {
31✔
UNCOV
127
                $parameters = $operation->getParameters() ?? new Parameters();
31✔
UNCOV
128
                foreach ($operation->getParameters() ?? [] as $key => $parameter) {
31✔
UNCOV
129
                    $key = $parameter->getKey() ?? $key;
3✔
UNCOV
130
                    $parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties);
3✔
UNCOV
131
                    $priority = $parameter->getPriority() ?? $internalPriority--;
3✔
UNCOV
132
                    $parameters->add($key, $parameter->withPriority($priority));
3✔
133
                }
134

UNCOV
135
                $graphQlOperations[$operationName] = $operation->withParameters($parameters);
31✔
136
            }
137

UNCOV
138
            $resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
31✔
139
        }
140

UNCOV
141
        return $resourceMetadataCollection;
42✔
142
    }
143

144
    private function addFilterMetadata(Parameter $parameter): Parameter
145
    {
UNCOV
146
        if (!($filterId = $parameter->getFilter())) {
3✔
UNCOV
147
            return $parameter;
3✔
148
        }
149

UNCOV
150
        $filter = \is_object($filterId) ? $filterId : $this->filterLocator->get($filterId);
3✔
151

UNCOV
152
        if (!$filter) {
3✔
153
            return $parameter;
×
154
        }
155

UNCOV
156
        if (null === $parameter->getSchema() && $filter instanceof HasSchemaFilterInterface) {
3✔
157
            if ($schema = $filter->getSchema($parameter)) {
×
158
                $parameter = $parameter->withSchema($schema);
×
159
            }
160
        }
161

UNCOV
162
        if (null === $parameter->getOpenApi() && $filter instanceof HasOpenApiParameterFilterInterface) {
3✔
163
            if ($openApiParameter = $filter->getOpenApiParameter($parameter)) {
×
164
                $parameter = $parameter->withOpenApi($openApiParameter);
×
165
            }
166
        }
167

UNCOV
168
        return $parameter;
3✔
169
    }
170

171
    /**
172
     * @param array<string, int> $properties
173
     */
174
    private function setDefaults(string $key, Parameter $parameter, string $resourceClass, array $properties): Parameter
175
    {
UNCOV
176
        if (null === $parameter->getKey()) {
3✔
UNCOV
177
            $parameter = $parameter->withKey($key);
3✔
178
        }
179

UNCOV
180
        $filter = $parameter->getFilter();
3✔
UNCOV
181
        if (\is_string($filter) && $this->filterLocator->has($filter)) {
3✔
UNCOV
182
            $filter = $this->filterLocator->get($filter);
3✔
183
        }
184

UNCOV
185
        if ($filter instanceof SerializerFilterInterface && null === $parameter->getProvider()) {
3✔
UNCOV
186
            $parameter = $parameter->withProvider('api_platform.serializer.filter_parameter_provider');
3✔
187
        }
188

189
        // Read filter description to populate the Parameter
UNCOV
190
        $description = $filter instanceof FilterInterface ? $filter->getDescription($resourceClass) : [];
3✔
UNCOV
191
        if (($schema = $description[$key]['schema'] ?? null) && null === $parameter->getSchema()) {
3✔
192
            $parameter = $parameter->withSchema($schema);
×
193
        }
194

UNCOV
195
        if (null === $parameter->getProperty() && ($property = $description[$key]['property'] ?? null)) {
3✔
UNCOV
196
            $parameter = $parameter->withProperty($property);
3✔
197
        }
198

UNCOV
199
        if (null === $parameter->getProperty() && isset($properties[$key])) {
3✔
UNCOV
200
            $parameter = $parameter->withProperty($key);
3✔
201
        }
202

UNCOV
203
        if (null === $parameter->getProperty() && $this->nameConverter && ($nameConvertedKey = $this->nameConverter->normalize($key)) && isset($properties[$nameConvertedKey])) {
3✔
204
            $parameter = $parameter->withProperty($key)->withExtraProperties(['_query_property' => $nameConvertedKey] + $parameter->getExtraProperties());
×
205
        }
206

UNCOV
207
        if (null === $parameter->getRequired() && ($required = $description[$key]['required'] ?? null)) {
3✔
208
            $parameter = $parameter->withRequired($required);
×
209
        }
210

UNCOV
211
        if (null === $parameter->getOpenApi() && ($openApi = $description[$key]['openapi'] ?? null) && $openApi instanceof OpenApiParameter) {
3✔
212
            $parameter = $parameter->withOpenApi($openApi);
×
213
        }
214

UNCOV
215
        $schema = $parameter->getSchema() ?? (($openApi = $parameter->getOpenApi()) ? $openApi->getSchema() : null);
3✔
216

217
        // Only add validation if the Symfony Validator is installed
UNCOV
218
        if (interface_exists(ValidatorInterface::class) && !$parameter->getConstraints()) {
3✔
UNCOV
219
            $parameter = $this->addSchemaValidation($parameter, $schema, $parameter->getRequired() ?? $description['required'] ?? false, $parameter->getOpenApi() ?: null);
3✔
220
        }
221

UNCOV
222
        return $parameter;
3✔
223
    }
224

225
    private function addSchemaValidation(Parameter $parameter, ?array $schema = null, bool $required = false, ?OpenApiParameter $openApi = null): Parameter
226
    {
UNCOV
227
        $assertions = [];
13✔
228

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

UNCOV
233
        if (false === ($allowEmptyValue ?? $openApi?->getAllowEmptyValue())) {
13✔
UNCOV
234
            $assertions[] = new NotBlank(allowNull: !$required);
8✔
235
        }
236

UNCOV
237
        if (isset($schema['exclusiveMinimum'])) {
13✔
UNCOV
238
            $assertions[] = new GreaterThan(value: $schema['exclusiveMinimum']);
4✔
239
        }
240

UNCOV
241
        if (isset($schema['exclusiveMaximum'])) {
13✔
UNCOV
242
            $assertions[] = new LessThan(value: $schema['exclusiveMaximum']);
4✔
243
        }
244

UNCOV
245
        if (isset($schema['minimum'])) {
13✔
UNCOV
246
            $assertions[] = new GreaterThanOrEqual(value: $schema['minimum']);
4✔
247
        }
248

UNCOV
249
        if (isset($schema['maximum'])) {
13✔
UNCOV
250
            $assertions[] = new LessThanOrEqual(value: $schema['maximum']);
4✔
251
        }
252

UNCOV
253
        if (isset($schema['pattern'])) {
13✔
UNCOV
254
            $assertions[] = new Regex($schema['pattern']);
4✔
255
        }
256

UNCOV
257
        if (isset($schema['maxLength']) || isset($schema['minLength'])) {
13✔
UNCOV
258
            $assertions[] = new Length(min: $schema['minLength'] ?? null, max: $schema['maxLength'] ?? null);
4✔
259
        }
260

UNCOV
261
        if (isset($schema['minItems']) || isset($schema['maxItems'])) {
13✔
UNCOV
262
            $assertions[] = new Count(min: $schema['minItems'] ?? null, max: $schema['maxItems'] ?? null);
4✔
263
        }
264

UNCOV
265
        if (isset($schema['multipleOf'])) {
13✔
UNCOV
266
            $assertions[] = new DivisibleBy(value: $schema['multipleOf']);
4✔
267
        }
268

UNCOV
269
        if ($schema['uniqueItems'] ?? false) {
13✔
UNCOV
270
            $assertions[] = new Unique();
4✔
271
        }
272

UNCOV
273
        if (isset($schema['enum'])) {
13✔
UNCOV
274
            $assertions[] = new Choice(choices: $schema['enum']);
6✔
275
        }
276

UNCOV
277
        if (isset($schema['type']) && 'array' === $schema['type']) {
13✔
UNCOV
278
            $assertions[] = new Type(type: 'array');
3✔
279
        }
280

UNCOV
281
        if (!$assertions) {
13✔
UNCOV
282
            return $parameter;
10✔
283
        }
284

UNCOV
285
        if (1 === \count($assertions)) {
9✔
UNCOV
286
            return $parameter->withConstraints($assertions[0]);
9✔
287
        }
288

UNCOV
289
        return $parameter->withConstraints($assertions);
3✔
290
    }
291

292
    private function addFilterValidation(HttpOperation $operation): Parameters
293
    {
UNCOV
294
        $parameters = new Parameters();
15✔
UNCOV
295
        $internalPriority = -1;
15✔
296

UNCOV
297
        foreach ($operation->getFilters() as $filter) {
15✔
UNCOV
298
            if (!$this->filterLocator->has($filter)) {
15✔
UNCOV
299
                continue;
2✔
300
            }
301

UNCOV
302
            $filter = $this->filterLocator->get($filter);
15✔
UNCOV
303
            foreach ($filter->getDescription($operation->getClass()) as $parameterName => $definition) {
15✔
UNCOV
304
                $key = $parameterName;
13✔
UNCOV
305
                $required = $definition['required'] ?? false;
13✔
UNCOV
306
                $schema = $definition['schema'] ?? null;
13✔
307

UNCOV
308
                $openApi = null;
13✔
UNCOV
309
                if (isset($definition['openapi']) && $definition['openapi'] instanceof OpenApiParameter) {
13✔
UNCOV
310
                    $openApi = $definition['openapi'];
7✔
311
                }
312

313
                // The query parameter validator forced this, lets maintain BC on filters
UNCOV
314
                if (true === $required && !$openApi) {
13✔
UNCOV
315
                    $openApi = new OpenApiParameter(name: $key, in: 'query', allowEmptyValue: false);
4✔
316
                }
317

UNCOV
318
                $parameters->add($key, $this->addSchemaValidation(
13✔
319
                    // we disable openapi and hydra on purpose as their docs comes from filters see the condition for addFilterValidation above
UNCOV
320
                    new QueryParameter(key: $key, property: $definition['property'] ?? null, priority: $internalPriority--, schema: $schema, openApi: false, hydra: false),
13✔
UNCOV
321
                    $schema,
13✔
UNCOV
322
                    $required,
13✔
UNCOV
323
                    $openApi
13✔
UNCOV
324
                ));
13✔
325
            }
326
        }
327

UNCOV
328
        return $parameters;
15✔
329
    }
330
}
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