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

api-platform / core / 14836358929

05 May 2025 12:24PM UTC coverage: 8.396% (-15.0%) from 23.443%
14836358929

push

github

soyuka
test: property info deprecation

0 of 300 new or added lines in 4 files covered. (0.0%)

2655 existing lines in 165 files now uncovered.

13444 of 160118 relevant lines covered (8.4%)

22.88 hits per line

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

89.42
/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\ApiProperty;
17
use ApiPlatform\Metadata\Exception\RuntimeException;
18
use ApiPlatform\Metadata\FilterInterface;
19
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
20
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
21
use ApiPlatform\Metadata\Operation;
22
use ApiPlatform\Metadata\Parameter;
23
use ApiPlatform\Metadata\ParameterProviderFilterInterface;
24
use ApiPlatform\Metadata\Parameters;
25
use ApiPlatform\Metadata\PropertiesAwareInterface;
26
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
27
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
29
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
30
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
31
use ApiPlatform\State\Util\StateOptionsTrait;
32
use Psr\Container\ContainerInterface;
33
use Psr\Log\LoggerInterface;
34
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
35

36
/**
37
 * Prepares Parameters documentation by reading its filter details and declaring an OpenApi parameter.
38
 *
39
 * @experimental
40
 */
41
final class ParameterResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
42
{
43
    use StateOptionsTrait;
44

45
    private array $localPropertyCache;
46

47
    public function __construct(
48
        private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
49
        private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory,
50
        private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
51
        private readonly ?ContainerInterface $filterLocator = null,
52
        private readonly ?NameConverterInterface $nameConverter = null,
53
        private readonly ?LoggerInterface $logger = null,
54
    ) {
55
    }
2,064✔
56

57
    public function create(string $resourceClass): ResourceMetadataCollection
58
    {
59
        $resourceMetadataCollection = $this->decorated?->create($resourceClass) ?? new ResourceMetadataCollection($resourceClass);
137✔
60

61
        foreach ($resourceMetadataCollection as $i => $resource) {
137✔
62
            $operations = $resource->getOperations();
130✔
63

64
            $internalPriority = -1;
130✔
65
            foreach ($operations as $operationName => $operation) {
130✔
66
                $parameters = $this->getDefaultParameters($operation, $resourceClass, $internalPriority);
130✔
67
                if (\count($parameters) > 0) {
130✔
68
                    $operations->add($operationName, $operation->withParameters($parameters));
31✔
69
                }
70
            }
71

72
            $resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());
130✔
73

74
            if (!$graphQlOperations = $resource->getGraphQlOperations()) {
130✔
75
                continue;
56✔
76
            }
77

78
            $internalPriority = -1;
109✔
79
            foreach ($graphQlOperations as $operationName => $operation) {
109✔
80
                $parameters = $this->getDefaultParameters($operation, $resourceClass, $internalPriority);
109✔
81
                if (\count($parameters) > 0) {
109✔
82
                    $graphQlOperations[$operationName] = $operation->withParameters($parameters);
4✔
83
                }
84
            }
85

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

89
        return $resourceMetadataCollection;
137✔
90
    }
91

92
    /**
93
     * @return array{propertyNames: string[], properties: array<string, ApiProperty>}
94
     */
95
    private function getProperties(string $resourceClass, ?Parameter $parameter = null): array
96
    {
97
        $k = $resourceClass.($parameter?->getProperties() ? ($parameter->getKey() ?? '') : '');
31✔
98
        if (isset($this->localPropertyCache[$k])) {
31✔
99
            return $this->localPropertyCache[$k];
27✔
100
        }
101

102
        $propertyNames = [];
31✔
103
        $properties = [];
31✔
104
        foreach ($parameter?->getProperties() ?? $this->propertyNameCollectionFactory->create($resourceClass) as $property) {
31✔
105
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
27✔
106
            if ($propertyMetadata->isReadable()) {
27✔
107
                $propertyNames[] = $property;
27✔
108
                $properties[$property] = $propertyMetadata;
27✔
109
            }
110
        }
111

112
        $this->localPropertyCache[$k] = ['propertyNames' => $propertyNames, 'properties' => $properties];
31✔
113

114
        return $this->localPropertyCache[$k];
31✔
115
    }
116

117
    private function getDefaultParameters(Operation $operation, string $resourceClass, int &$internalPriority): Parameters
118
    {
119
        $propertyNames = $properties = [];
130✔
120
        $parameters = $operation->getParameters() ?? new Parameters();
130✔
121
        foreach ($parameters as $key => $parameter) {
130✔
122
            ['propertyNames' => $propertyNames, 'properties' => $properties] = $this->getProperties($resourceClass, $parameter);
31✔
123
            if (null === $parameter->getProvider() && (($f = $parameter->getFilter()) && $f instanceof ParameterProviderFilterInterface)) {
31✔
UNCOV
124
                $parameters->add($key, $parameter->withProvider($f->getParameterProvider()));
×
125
            }
126

127
            if (':property' === $key) {
31✔
128
                foreach ($propertyNames as $property) {
4✔
129
                    $converted = $this->nameConverter?->denormalize($property) ?? $property;
4✔
130
                    $propertyParameter = $this->setDefaults($converted, $parameter, $resourceClass, $properties, $operation);
4✔
131
                    $priority = $propertyParameter->getPriority() ?? $internalPriority--;
4✔
132
                    $parameters->add($converted, $propertyParameter->withPriority($priority)->withKey($converted));
4✔
133
                }
134

135
                $parameters->remove($key, $parameter::class);
4✔
136
                continue;
4✔
137
            }
138

139
            $key = $parameter->getKey() ?? $key;
31✔
140

141
            if (str_contains($key, ':property') || ((($f = $parameter->getFilter()) && is_a($f, PropertiesAwareInterface::class, true)) || $parameter instanceof PropertiesAwareInterface)) {
31✔
142
                $p = [];
12✔
143
                foreach ($propertyNames as $prop) {
12✔
144
                    $p[$this->nameConverter?->denormalize($prop) ?? $prop] = $prop;
10✔
145
                }
146

147
                $parameter = $parameter->withExtraProperties($parameter->getExtraProperties() + ['_properties' => $p]);
12✔
148
            }
149

150
            $parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties, $operation);
31✔
151
            $priority = $parameter->getPriority() ?? $internalPriority--;
31✔
152
            $parameters->add($key, $parameter->withPriority($priority));
31✔
153
        }
154

155
        return $parameters;
130✔
156
    }
157

158
    private function addFilterMetadata(Parameter $parameter): Parameter
159
    {
160
        if (!($filterId = $parameter->getFilter())) {
31✔
161
            return $parameter;
14✔
162
        }
163

164
        if (!\is_object($filterId) && !$this->filterLocator->has($filterId)) {
23✔
UNCOV
165
            return $parameter;
×
166
        }
167

168
        $filter = \is_object($filterId) ? $filterId : $this->filterLocator->get($filterId);
23✔
169

170
        if ($filter instanceof ParameterProviderFilterInterface) {
23✔
UNCOV
171
            $parameter = $parameter->withProvider($filter::getParameterProvider());
×
172
        }
173

174
        if (!$filter) {
23✔
UNCOV
175
            return $parameter;
×
176
        }
177

178
        if (null === $parameter->getSchema() && $filter instanceof JsonSchemaFilterInterface && $schema = $filter->getSchema($parameter)) {
23✔
179
            $parameter = $parameter->withSchema($schema);
21✔
180
        }
181

182
        if (null === $parameter->getOpenApi() && $filter instanceof OpenApiParameterFilterInterface && ($openApiParameter = $filter->getOpenApiParameters($parameter))) {
23✔
183
            $parameter = $parameter->withOpenApi($openApiParameter);
13✔
184
        }
185

186
        return $parameter;
23✔
187
    }
188

189
    /**
190
     * @param array<string, ApiProperty> $properties
191
     */
192
    private function setDefaults(string $key, Parameter $parameter, string $resourceClass, array $properties, Operation $operation): Parameter
193
    {
194
        if (null === $parameter->getKey()) {
31✔
195
            $parameter = $parameter->withKey($key);
31✔
196
        }
197

198
        $filter = $parameter->getFilter();
31✔
199
        if (\is_string($filter) && $this->filterLocator->has($filter)) {
31✔
200
            $filter = $this->filterLocator->get($filter);
9✔
201
        }
202

203
        if ($filter instanceof SerializerFilterInterface && null === $parameter->getProvider()) {
31✔
204
            $parameter = $parameter->withProvider('api_platform.serializer.filter_parameter_provider');
4✔
205
        }
206
        $currentKey = $key;
31✔
207
        if (null === $parameter->getProperty() && isset($properties[$key])) {
31✔
208
            $parameter = $parameter->withProperty($key);
19✔
209
        }
210

211
        if (null === $parameter->getProperty() && $this->nameConverter && ($nameConvertedKey = $this->nameConverter->normalize($key)) && isset($properties[$nameConvertedKey])) {
31✔
UNCOV
212
            $parameter = $parameter->withProperty($key)->withExtraProperties(['_query_property' => $nameConvertedKey] + $parameter->getExtraProperties());
×
UNCOV
213
            $currentKey = $nameConvertedKey;
×
214
        }
215

216
        if ($this->nameConverter && $property = $parameter->getProperty()) {
31✔
217
            $parameter = $parameter->withProperty($this->nameConverter->normalize($property));
23✔
218
        }
219

220
        if (isset($properties[$currentKey]) && ($eloquentRelation = ($properties[$currentKey]->getExtraProperties()['eloquent_relation'] ?? null)) && isset($eloquentRelation['foreign_key'])) {
31✔
UNCOV
221
            $parameter = $parameter->withExtraProperties(['_query_property' => $eloquentRelation['foreign_key']] + $parameter->getExtraProperties());
×
222
        }
223

224
        $parameter = $this->addFilterMetadata($parameter);
31✔
225

226
        if ($filter instanceof FilterInterface) {
31✔
227
            try {
228
                return $this->getLegacyFilterMetadata($parameter, $operation, $filter);
23✔
229
            } catch (RuntimeException $exception) {
14✔
230
                $this->logger?->alert($exception->getMessage(), ['exception' => $exception]);
14✔
231

232
                return $parameter;
14✔
233
            }
234
        }
235

236
        return $parameter;
14✔
237
    }
238

239
    private function getLegacyFilterMetadata(Parameter $parameter, Operation $operation, FilterInterface $filter): Parameter
240
    {
241
        $description = $filter->getDescription($this->getStateOptionsClass($operation, $operation->getClass()));
23✔
242
        $key = $parameter->getKey();
11✔
243
        if (($schema = $description[$key]['schema'] ?? null) && null === $parameter->getSchema()) {
11✔
UNCOV
244
            $parameter = $parameter->withSchema($schema);
×
245
        }
246

247
        if (null === $parameter->getProperty() && ($property = $description[$key]['property'] ?? null)) {
11✔
UNCOV
248
            $parameter = $parameter->withProperty($property);
×
249
        }
250

251
        if (null === $parameter->getRequired() && ($required = $description[$key]['required'] ?? null)) {
11✔
UNCOV
252
            $parameter = $parameter->withRequired($required);
×
253
        }
254

255
        if (null === $parameter->getOpenApi() && ($openApi = $description[$key]['openapi'] ?? null) && $openApi instanceof OpenApiParameter) {
11✔
UNCOV
256
            $parameter = $parameter->withOpenApi($openApi);
×
257
        }
258

259
        return $parameter;
11✔
260
    }
261
}
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