From 9f05487ffb15b072314869d8c91984f9772f06ab Mon Sep 17 00:00:00 2001 From: Rhodri Pugh Date: Wed, 1 Jul 2026 15:55:02 +0100 Subject: [PATCH] fix(openapi): correct parameter name when QueryParameter remaps a filter property When a QueryParameter uses `property` to remap a filter's property name (e.g. `order[sort_by]` mapped to property `rating`), OpenApiFactory used `str_replace(, , )` to rewrite the filter description key. Because both the description key (`order[rating]`) and the replacement key (`order[sort_by]`) share the `order[...]` wrapper, the naive str_replace produced `order[order[sort_by]]`. Fix: skip filter description entries that don't match the mapped property and use the QueryParameter key directly as the parameter name. --- src/OpenApi/Factory/OpenApiFactory.php | 5 +++- .../Entity/ProductWithQueryParameter.php | 6 +++++ tests/Functional/Parameters/DoctrineTest.php | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 6273ea8ba51..3e58fcaa26c 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -346,7 +346,10 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection if ($d = $filter->getDescription($entityClass)) { foreach ($d as $name => $description) { if ($prop = $p->getProperty()) { - $name = str_replace($prop, $key, $name); + if (($description['property'] ?? null) !== $prop) { + continue; + } + $name = $key; } $openapiParameters[] = $this->getFilterParameter($name, $description, $operation->getShortName(), $f); diff --git a/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php b/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php index f3eee943714..50bf0747686 100644 --- a/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php +++ b/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php @@ -16,6 +16,7 @@ use ApiPlatform\Doctrine\Orm\Filter\ExactFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter; +use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\QueryParameter; @@ -23,6 +24,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] +#[ApiFilter(OrderFilter::class, alias: 'product_order_filter', properties: ['rating'])] #[ApiResource( operations: [ new GetCollection( @@ -46,6 +48,10 @@ filter: new OrderFilter(), properties: ['rating'] ), + 'order[sort_by]' => new QueryParameter( + filter: 'product_order_filter', + property: 'rating', + ), 'exactBrand' => new QueryParameter( filter: new ExactFilter(), property: 'brand', diff --git a/tests/Functional/Parameters/DoctrineTest.php b/tests/Functional/Parameters/DoctrineTest.php index abce8d43447..02f79c0e749 100644 --- a/tests/Functional/Parameters/DoctrineTest.php +++ b/tests/Functional/Parameters/DoctrineTest.php @@ -470,4 +470,27 @@ public static function openApiParameterDocumentationProvider(): array ], ]; } + + public function testQueryParameterPropertyRemapOpenApiParameterName(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('Not tested with mongodb.'); + } + + $resource = ProductWithQueryParameter::class; + $this->recreateSchema([$resource]); + + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $this->assertResponseIsSuccessful(); + $openApiDoc = $response->toArray(); + + $parameters = $openApiDoc['paths']['/product_with_query_parameters']['get']['parameters']; + $parameterNames = array_column($parameters, 'name'); + + $this->assertContains('order[sort_by]', $parameterNames, 'Parameter order[sort_by] should be present'); + $this->assertNotContains('order[order[sort_by]]', $parameterNames, 'Parameter name should not be double-wrapped as order[order[sort_by]]'); + } }