diff --git a/src/Doctrine/Common/Filter/OpenApiFilterTrait.php b/src/Doctrine/Common/Filter/OpenApiFilterTrait.php index 5371451f10..2ff8b608e1 100644 --- a/src/Doctrine/Common/Filter/OpenApiFilterTrait.php +++ b/src/Doctrine/Common/Filter/OpenApiFilterTrait.php @@ -24,18 +24,12 @@ trait OpenApiFilterTrait public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null { $schema = $parameter->getSchema(); - $isArraySchema = 'array' === ($schema['type'] ?? null); - $castToArray = $parameter->getCastToArray(); - - // Use non-array notation if: - // 1. Schema type is explicitly set to a non-array type (string, number, etc.) - // 2. OR castToArray is explicitly false - $hasNonArraySchema = null !== $schema && !$isArraySchema; - - if ($hasNonArraySchema || false === $castToArray) { + if (false === $parameter->getCastToArray() || (isset($schema['type']) && 'array' !== $schema['type'])) { return new OpenApiParameter(name: $parameter->getKey(), in: 'query'); } - return new OpenApiParameter(name: $parameter->getKey().'[]', in: 'query', style: 'deepObject', explode: true); + $arraySchema = ['type' => 'array', 'items' => $schema ?? ['type' => 'string']]; + + return new OpenApiParameter(name: $parameter->getKey().'[]', in: 'query', style: 'deepObject', explode: true, schema: $arraySchema); } } diff --git a/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php b/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php index dbe6391361..cd0c9fdc52 100644 --- a/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php +++ b/tests/Fixtures/TestBundle/Entity/ProductWithQueryParameter.php @@ -56,6 +56,11 @@ property: 'category', castToArray: false ), + 'tags' => new QueryParameter( + filter: new ExactFilter(), + property: 'tags', + schema: ['anyOf' => [['type' => 'array', 'items' => ['type' => 'string']], ['type' => 'string']]] + ), ] ), ] diff --git a/tests/Functional/Parameters/DoctrineTest.php b/tests/Functional/Parameters/DoctrineTest.php index c74803d2e5..f4be941e25 100644 --- a/tests/Functional/Parameters/DoctrineTest.php +++ b/tests/Functional/Parameters/DoctrineTest.php @@ -302,7 +302,7 @@ private function loadProductFixtures(string $resourceClass): void } #[DataProvider('openApiParameterDocumentationProvider')] - public function testOpenApiParameterDocumentation(string $parameterName, bool $shouldHaveArrayNotation, string $expectedStyle, bool $expectedExplode, ?string $expectedSchemaType = null, string $expectedDescription = ''): void + public function testOpenApiParameterDocumentation(string $parameterName, bool $shouldHaveArrayNotation, string $expectedStyle, bool $expectedExplode, string $expectedDescription = '', ?array $expectedSchema = null): void { if ($this->isMongoDB()) { $this->markTestSkipped('Not tested with mongodb.'); @@ -335,13 +335,14 @@ public function testOpenApiParameterDocumentation(string $parameterName, bool $s $this->assertSame('query', $foundParameter['in']); $this->assertFalse($foundParameter['required']); - if ($expectedSchemaType) { - $this->assertSame($expectedSchemaType, $foundParameter['schema']['type'], \sprintf('Parameter schema type should be %s', $expectedSchemaType)); - } - if (isset($foundParameter['expectedDescription'])) { $this->assertSame($expectedDescription, $foundParameter['description'] ?? '', \sprintf('Description should be %s', $expectedDescription)); } + + if ($expectedSchema) { + $this->assertSame($expectedSchema, $foundParameter['schema'], 'Parameter schema should match expected schema'); + } + $this->assertSame($expectedStyle, $foundParameter['style'] ?? 'form', \sprintf('Style should be %s', $expectedStyle)); $this->assertSame($expectedExplode, $foundParameter['explode'] ?? false, \sprintf('Explode should be %s', $expectedExplode ? 'true' : 'false')); } @@ -354,29 +355,40 @@ public static function openApiParameterDocumentationProvider(): array 'shouldHaveArrayNotation' => true, 'expectedStyle' => 'deepObject', 'expectedExplode' => true, - 'expectedSchemaType' => 'string', + 'expectedDescription' => '', + 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string']], ], 'default behavior with an extra description' => [ 'parameterName' => 'brandWithDescription', 'shouldHaveArrayNotation' => true, 'expectedStyle' => 'deepObject', 'expectedExplode' => true, - 'expectedSchemaType' => 'string', 'expectedDescription' => 'Extra description about the filter', + 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string']], ], 'explicit schema type string should not use array notation' => [ 'parameterName' => 'exactBrand', 'shouldHaveArrayNotation' => false, 'expectedStyle' => 'form', 'expectedExplode' => false, - 'expectedSchemaType' => 'string', + 'expectedDescription' => '', + 'expectedSchema' => ['type' => 'string'], ], 'castToArray false should not use array notation' => [ 'parameterName' => 'exactCategory', 'shouldHaveArrayNotation' => false, 'expectedStyle' => 'form', 'expectedExplode' => false, - 'expectedSchemaType' => 'string', + 'expectedDescription' => '', + 'expectedSchema' => ['type' => 'string'], + ], + 'with schema and default castToArray should wrap schema in array type' => [ + 'parameterName' => 'tags', + 'shouldHaveArrayNotation' => true, + 'expectedStyle' => 'deepObject', + 'expectedExplode' => true, + 'expectedDescription' => '', + 'expectedSchema' => ['type' => 'array', 'items' => ['anyOf' => [['type' => 'array', 'items' => ['type' => 'string']], ['type' => 'string']]]], ], ]; }