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

api-platform / core / 13175753759

06 Feb 2025 09:30AM UTC coverage: 0.0% (-7.7%) from 7.663%
13175753759

Pull #6947

github

web-flow
Merge 432a515ad into de2d298e3
Pull Request #6947: fix(metadata): remove temporary fix for ArrayCollection

0 of 1 new or added line in 1 file covered. (0.0%)

11655 existing lines in 385 files now uncovered.

0 of 47351 relevant lines covered (0.0%)

0.0 hits per line

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

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

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

44
    public function __construct(
45
        private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
46
        private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory,
47
        private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
48
        private readonly ?ContainerInterface $filterLocator = null,
49
        private readonly ?NameConverterInterface $nameConverter = null,
50
    ) {
UNCOV
51
    }
×
52

53
    public function create(string $resourceClass): ResourceMetadataCollection
54
    {
UNCOV
55
        $resourceMetadataCollection = $this->decorated?->create($resourceClass) ?? new ResourceMetadataCollection($resourceClass);
×
56

UNCOV
57
        foreach ($resourceMetadataCollection as $i => $resource) {
×
UNCOV
58
            $operations = $resource->getOperations();
×
59

UNCOV
60
            $internalPriority = -1;
×
UNCOV
61
            foreach ($operations as $operationName => $operation) {
×
UNCOV
62
                $parameters = $this->getDefaultParameters($operation, $resourceClass, $internalPriority);
×
UNCOV
63
                if (\count($parameters) > 0) {
×
UNCOV
64
                    $operations->add($operationName, $operation->withParameters($parameters));
×
65
                }
66
            }
67

UNCOV
68
            $resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());
×
69

UNCOV
70
            if (!$graphQlOperations = $resource->getGraphQlOperations()) {
×
UNCOV
71
                continue;
×
72
            }
73

UNCOV
74
            $internalPriority = -1;
×
UNCOV
75
            foreach ($graphQlOperations as $operationName => $operation) {
×
UNCOV
76
                $parameters = $this->getDefaultParameters($operation, $resourceClass, $internalPriority);
×
UNCOV
77
                if (\count($parameters) > 0) {
×
UNCOV
78
                    $graphQlOperations[$operationName] = $operation->withParameters($parameters);
×
79
                }
80
            }
81

UNCOV
82
            $resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
×
83
        }
84

UNCOV
85
        return $resourceMetadataCollection;
×
86
    }
87

88
    /**
89
     * @return array{propertyNames: string[], properties: array<string, ApiProperty>}
90
     */
91
    private function getProperties(string $resourceClass): array
92
    {
UNCOV
93
        if (isset($this->localPropertyCache[$resourceClass])) {
×
UNCOV
94
            return $this->localPropertyCache[$resourceClass];
×
95
        }
96

UNCOV
97
        $propertyNames = [];
×
UNCOV
98
        $properties = [];
×
UNCOV
99
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
×
UNCOV
100
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
×
UNCOV
101
            if ($propertyMetadata->isReadable()) {
×
UNCOV
102
                $propertyNames[] = $property;
×
UNCOV
103
                $properties[$property] = $propertyMetadata;
×
104
            }
105
        }
106

UNCOV
107
        $this->localPropertyCache = [$resourceClass => ['propertyNames' => $propertyNames, 'properties' => $properties]];
×
108

UNCOV
109
        return $this->localPropertyCache[$resourceClass];
×
110
    }
111

112
    private function getDefaultParameters(Operation $operation, string $resourceClass, int &$internalPriority): Parameters
113
    {
UNCOV
114
        ['propertyNames' => $propertyNames, 'properties' => $properties] = $this->getProperties($resourceClass);
×
UNCOV
115
        $parameters = $operation->getParameters() ?? new Parameters();
×
UNCOV
116
        foreach ($parameters as $key => $parameter) {
×
UNCOV
117
            if (':property' === $key) {
×
UNCOV
118
                foreach ($propertyNames as $property) {
×
UNCOV
119
                    $converted = $this->nameConverter?->denormalize($property) ?? $property;
×
UNCOV
120
                    $propertyParameter = $this->setDefaults($converted, $parameter, $resourceClass, $properties, $operation);
×
UNCOV
121
                    $priority = $propertyParameter->getPriority() ?? $internalPriority--;
×
UNCOV
122
                    $parameters->add($converted, $propertyParameter->withPriority($priority)->withKey($converted));
×
123
                }
124

UNCOV
125
                $parameters->remove($key, $parameter::class);
×
UNCOV
126
                continue;
×
127
            }
128

UNCOV
129
            $key = $parameter->getKey() ?? $key;
×
130

UNCOV
131
            if (str_contains($key, ':property') || (($f = $parameter->getFilter()) && is_a($f, PropertiesAwareInterface::class, true)) || $parameter instanceof PropertiesAwareInterface) {
×
UNCOV
132
                $p = [];
×
UNCOV
133
                foreach ($propertyNames as $prop) {
×
UNCOV
134
                    $p[$this->nameConverter?->denormalize($prop) ?? $prop] = $prop;
×
135
                }
136

UNCOV
137
                $parameter = $parameter->withExtraProperties($parameter->getExtraProperties() + ['_properties' => $p]);
×
138
            }
139

UNCOV
140
            $parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties, $operation);
×
UNCOV
141
            $priority = $parameter->getPriority() ?? $internalPriority--;
×
UNCOV
142
            $parameters->add($key, $parameter->withPriority($priority));
×
143
        }
144

UNCOV
145
        return $parameters;
×
146
    }
147

148
    private function addFilterMetadata(Parameter $parameter): Parameter
149
    {
UNCOV
150
        if (!($filterId = $parameter->getFilter())) {
×
UNCOV
151
            return $parameter;
×
152
        }
153

UNCOV
154
        if (!\is_object($filterId) && !$this->filterLocator->has($filterId)) {
×
155
            return $parameter;
×
156
        }
157

UNCOV
158
        $filter = \is_object($filterId) ? $filterId : $this->filterLocator->get($filterId);
×
159

UNCOV
160
        if ($filter instanceof ParameterProviderFilterInterface) {
×
161
            $parameter = $parameter->withProvider($filter::getParameterProvider());
×
162
        }
163

UNCOV
164
        if (!$filter) {
×
165
            return $parameter;
×
166
        }
167

UNCOV
168
        if (null === $parameter->getSchema() && $filter instanceof JsonSchemaFilterInterface && $schema = $filter->getSchema($parameter)) {
×
UNCOV
169
            $parameter = $parameter->withSchema($schema);
×
170
        }
171

UNCOV
172
        if (null === $parameter->getOpenApi() && $filter instanceof OpenApiParameterFilterInterface && ($openApiParameter = $filter->getOpenApiParameters($parameter))) {
×
UNCOV
173
            $parameter = $parameter->withOpenApi($openApiParameter);
×
174
        }
175

UNCOV
176
        return $parameter;
×
177
    }
178

179
    /**
180
     * @param array<string, ApiProperty> $properties
181
     */
182
    private function setDefaults(string $key, Parameter $parameter, string $resourceClass, array $properties, Operation $operation): Parameter
183
    {
UNCOV
184
        if (null === $parameter->getKey()) {
×
UNCOV
185
            $parameter = $parameter->withKey($key);
×
186
        }
187

UNCOV
188
        $filter = $parameter->getFilter();
×
UNCOV
189
        if (\is_string($filter) && $this->filterLocator->has($filter)) {
×
UNCOV
190
            $filter = $this->filterLocator->get($filter);
×
191
        }
192

UNCOV
193
        if ($filter instanceof SerializerFilterInterface && null === $parameter->getProvider()) {
×
UNCOV
194
            $parameter = $parameter->withProvider('api_platform.serializer.filter_parameter_provider');
×
195
        }
196

197
        // Read filter description to populate the Parameter
UNCOV
198
        $description = $filter instanceof FilterInterface ? $filter->getDescription($this->getFilterClass($operation)) : [];
×
UNCOV
199
        if (($schema = $description[$key]['schema'] ?? null) && null === $parameter->getSchema()) {
×
200
            $parameter = $parameter->withSchema($schema);
×
201
        }
202

UNCOV
203
        if (($openapi = $description[$key]['openapi'] ?? null) && null === $parameter->getOpenApi() && $openapi instanceof OpenApiParameter) {
×
204
            $parameter = $parameter->withOpenApi($openapi);
×
205
        }
206

UNCOV
207
        $currentKey = $key;
×
UNCOV
208
        if (null === $parameter->getProperty() && isset($properties[$key])) {
×
UNCOV
209
            $parameter = $parameter->withProperty($key);
×
210
        }
211

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

UNCOV
217
        if ($this->nameConverter && $property = $parameter->getProperty()) {
×
UNCOV
218
            $parameter = $parameter->withProperty($this->nameConverter->normalize($property));
×
219
        }
220

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

UNCOV
225
        if (null === $parameter->getRequired() && ($required = $description[$key]['required'] ?? null)) {
×
226
            $parameter = $parameter->withRequired($required);
×
227
        }
228

UNCOV
229
        return $this->addFilterMetadata($parameter);
×
230
    }
231

232
    private function getFilterClass(Operation $operation): ?string
233
    {
UNCOV
234
        $stateOptions = $operation->getStateOptions();
×
UNCOV
235
        if ($stateOptions instanceof DoctrineORMOptions) {
×
UNCOV
236
            return $stateOptions->getEntityClass();
×
237
        }
UNCOV
238
        if ($stateOptions instanceof DoctrineODMOptions) {
×
UNCOV
239
            return $stateOptions->getDocumentClass();
×
240
        }
241

UNCOV
242
        return $operation->getClass();
×
243
    }
244
}
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