diff --git a/src/Doctrine/Common/Filter/OpenApiFilterTrait.php b/src/Doctrine/Common/Filter/OpenApiFilterTrait.php index 2af0d951df3..77cc76b5ff4 100644 --- a/src/Doctrine/Common/Filter/OpenApiFilterTrait.php +++ b/src/Doctrine/Common/Filter/OpenApiFilterTrait.php @@ -24,26 +24,25 @@ trait OpenApiFilterTrait public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null { $schema = $parameter->getSchema(); - if (false === $parameter->getCastToArray() || (isset($schema['type']) && 'array' !== $schema['type'])) { - return new OpenApiParameter(name: $parameter->getKey(), in: 'query'); - } + $castToArray = $parameter->getCastToArray(); - if ('array' === ($schema['type'] ?? null)) { - $arraySchema = $schema; - } else { - $arraySchema = ['type' => 'array', 'items' => $schema ?? ['type' => 'string']]; + if (false === $castToArray) { + return new OpenApiParameter(name: $parameter->getKey(), in: 'query', schema: $schema ?? []); } + $arraySchema = 'array' === ($schema['type'] ?? null) + ? $schema + : ['type' => 'array', 'items' => $schema ?? ['type' => 'string']]; $arrayParameter = new OpenApiParameter(name: $parameter->getKey().'[]', in: 'query', style: 'deepObject', explode: true, schema: $arraySchema); - // When castToArray is null (default), both singular and array forms are accepted - if (null === $parameter->getCastToArray()) { - return [ - new OpenApiParameter(name: $parameter->getKey(), in: 'query'), - $arrayParameter, - ]; + if (true === $castToArray) { + return $arrayParameter; } - return $arrayParameter; + // When castToArray is null (default), both singular and array forms are accepted. + return [ + new OpenApiParameter(name: $parameter->getKey(), in: 'query', schema: $schema ?? []), + $arrayParameter, + ]; } } diff --git a/tests/Fixtures/TestBundle/ApiResource/ExactFilterSchemaParameter/ExactFilterSchemaParameter.php b/tests/Fixtures/TestBundle/ApiResource/ExactFilterSchemaParameter/ExactFilterSchemaParameter.php new file mode 100644 index 00000000000..98269b3741e --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/ExactFilterSchemaParameter/ExactFilterSchemaParameter.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\ExactFilterSchemaParameter; + +use ApiPlatform\Doctrine\Orm\Filter\ExactFilter; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\QueryParameter; + +#[GetCollection( + uriTemplate: '/exact_filter_schema_parameters', + normalizationContext: ['hydra_prefix' => false], + parameters: [ + // Scalar schema, default castToArray (null) -> both singular and array variants. + 'code' => new QueryParameter( + filter: new ExactFilter(), + property: 'code', + schema: ['type' => 'integer'], + ), + // Scalar schema, castToArray: true -> array variant only. + 'codeArrayCast' => new QueryParameter( + filter: new ExactFilter(), + property: 'code', + schema: ['type' => 'integer'], + castToArray: true, + ), + // Scalar schema, castToArray: false -> singular variant only. + 'codeNoArray' => new QueryParameter( + filter: new ExactFilter(), + property: 'code', + schema: ['type' => 'integer'], + castToArray: false, + ), + // Already an array schema, castToArray: true -> single array variant, no duplicate. + 'codeExplicitArray' => new QueryParameter( + filter: new ExactFilter(), + property: 'code', + schema: ['type' => 'array', 'items' => ['type' => 'integer']], + castToArray: true, + ), + ], + provider: [self::class, 'provide'], +)] +class ExactFilterSchemaParameter +{ + public function __construct( + public int $code = 0, + ) { + } + + /** + * @return self[] + */ + public static function provide(Operation $operation): array + { + return []; + } +} diff --git a/tests/Functional/Parameters/ExactFilterSchemaParameterTest.php b/tests/Functional/Parameters/ExactFilterSchemaParameterTest.php new file mode 100644 index 00000000000..594df8891cb --- /dev/null +++ b/tests/Functional/Parameters/ExactFilterSchemaParameterTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Functional\Parameters; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\ExactFilterSchemaParameter\ExactFilterSchemaParameter; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class ExactFilterSchemaParameterTest extends ApiTestCase +{ + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ExactFilterSchemaParameter::class]; + } + + /** + * @return array}> + */ + private function getOpenApiParametersByName(): array + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + $this->assertResponseIsSuccessful(); + $openApiDoc = $response->toArray(); + + $parameters = $openApiDoc['paths']['/exact_filter_schema_parameters']['get']['parameters']; + + $byName = []; + foreach ($parameters as $parameter) { + $byName[$parameter['name']] = $parameter; + } + + return $byName; + } + + public function testScalarSchemaWithDefaultCastToArrayExposesBothVariants(): void + { + $parameters = $this->getOpenApiParametersByName(); + + $this->assertArrayHasKey('code', $parameters); + $this->assertArrayHasKey('code[]', $parameters); + + $this->assertSame('integer', $parameters['code']['schema']['type']); + + $this->assertSame('array', $parameters['code[]']['schema']['type']); + $this->assertSame('integer', $parameters['code[]']['schema']['items']['type']); + } + + public function testScalarSchemaWithCastToArrayTrueExposesArrayVariantOnly(): void + { + $parameters = $this->getOpenApiParametersByName(); + + $this->assertArrayNotHasKey('codeArrayCast', $parameters); + $this->assertArrayHasKey('codeArrayCast[]', $parameters); + + $this->assertSame('array', $parameters['codeArrayCast[]']['schema']['type']); + $this->assertSame('integer', $parameters['codeArrayCast[]']['schema']['items']['type']); + } + + public function testScalarSchemaWithCastToArrayFalseExposesSingularVariantOnly(): void + { + $parameters = $this->getOpenApiParametersByName(); + + $this->assertArrayHasKey('codeNoArray', $parameters); + $this->assertArrayNotHasKey('codeNoArray[]', $parameters); + + $this->assertSame('integer', $parameters['codeNoArray']['schema']['type']); + } + + public function testArraySchemaWithCastToArrayTrueExposesSingleArrayVariant(): void + { + $parameters = $this->getOpenApiParametersByName(); + + $this->assertArrayNotHasKey('codeExplicitArray', $parameters); + $this->assertArrayHasKey('codeExplicitArray[]', $parameters); + + $this->assertSame('array', $parameters['codeExplicitArray[]']['schema']['type']); + $this->assertSame('integer', $parameters['codeExplicitArray[]']['schema']['items']['type']); + } +}