diff --git a/src/Metadata/Parameter.php b/src/Metadata/Parameter.php index 784e886949e..6ffc2d401dd 100644 --- a/src/Metadata/Parameter.php +++ b/src/Metadata/Parameter.php @@ -56,6 +56,7 @@ public function __construct( protected ?bool $castToArray = null, protected ?bool $castToNativeType = null, protected mixed $castFn = null, + protected mixed $default = null, ) { } @@ -139,7 +140,7 @@ public function getSecurityMessage(): ?string */ public function getValue(mixed $default = new ParameterNotFound()): mixed { - return $this->extraProperties['_api_values'] ?? $default; + return $this->extraProperties['_api_values'] ?? $this->default ?? $default; } /** @@ -370,4 +371,17 @@ public function withCastFn(mixed $castFn): self return $self; } + + public function getDefault(): mixed + { + return $this->default; + } + + public function withDefault(mixed $default): self + { + $self = clone $this; + $self->default = $default; + + return $self; + } } diff --git a/src/Metadata/Tests/ParameterTest.php b/src/Metadata/Tests/ParameterTest.php index 3e38798c16b..04bf04a444d 100644 --- a/src/Metadata/Tests/ParameterTest.php +++ b/src/Metadata/Tests/ParameterTest.php @@ -13,16 +13,40 @@ namespace ApiPlatform\Metadata\Tests; +use ApiPlatform\Metadata\Parameter; use ApiPlatform\Metadata\QueryParameter; use ApiPlatform\State\ParameterNotFound; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ParameterTest extends TestCase { public function testDefaultValue(): void { - $parameter = new QueryParameter(); - $this->assertSame('test', $parameter->getValue('test')); - $this->assertInstanceOf(ParameterNotFound::class, $parameter->getValue()); + $this->assertInstanceOf(ParameterNotFound::class, (new QueryParameter())->getValue()); + } + + #[DataProvider('provideDefaultValueCases')] + public function testDefaultValueWithFallbackValue(Parameter $parameter, mixed $fallbackValue, mixed $expectedDefault): void + { + $this->assertSame($expectedDefault, $parameter->getValue($fallbackValue)); + } + + /** @return iterable */ + public static function provideDefaultValueCases(): iterable + { + $fallbackValue = new ParameterNotFound(); + + yield 'no default specified' => [ + new QueryParameter(), $fallbackValue, $fallbackValue, + ]; + + yield 'with a default specified' => [ + new QueryParameter(default: false), $fallbackValue, false, + ]; + + yield 'with null as default' => [ + new QueryParameter(default: null), $fallbackValue, $fallbackValue, + ]; } } diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 740f302032b..b3ffa5fc0d6 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -337,6 +337,11 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection } $in = $p instanceof HeaderParameterInterface ? 'header' : 'query'; + $defaultSchema = ['type' => 'string']; + if (null !== $p->getDefault()) { + $defaultSchema['default'] = $p->getDefault(); + } + $defaultParameter = new Parameter( $key, $in, @@ -344,7 +349,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $p->getRequired() ?? false, false, null, - $p->getSchema() ?? ['type' => 'string'], + $p->getSchema() ?? $defaultSchema, ); $linkParameter = $p->getOpenApi(); diff --git a/src/State/Provider/ParameterProvider.php b/src/State/Provider/ParameterProvider.php index 08d8bbc7350..05bd072cb55 100644 --- a/src/State/Provider/ParameterProvider.php +++ b/src/State/Provider/ParameterProvider.php @@ -79,7 +79,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c unset($parameter->getExtraProperties()['_api_values']); } - if (null !== ($default = $parameter->getSchema()['default'] ?? null) && $value instanceof ParameterNotFound) { + if (null !== ($default = $parameter->getDefault() ?? $parameter->getSchema()['default'] ?? null) && $value instanceof ParameterNotFound) { $value = $default; } @@ -128,7 +128,7 @@ private function handlePathParameters(HttpOperation $operation, array $uriVariab unset($uriVariable->getExtraProperties()['_api_values']); } - if (($default = $uriVariable->getSchema()['default'] ?? false) && ($value instanceof ParameterNotFound || !$value)) { + if (($default = $uriVariable->getDefault() ?? $uriVariable->getSchema()['default'] ?? false) && ($value instanceof ParameterNotFound || !$value)) { $value = $default; } diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue7354/BooleanQueryParameter.php b/tests/Fixtures/TestBundle/ApiResource/Issue7354/BooleanQueryParameter.php index 6f8198dd18f..cefd1825b65 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Issue7354/BooleanQueryParameter.php +++ b/tests/Fixtures/TestBundle/ApiResource/Issue7354/BooleanQueryParameter.php @@ -31,6 +31,9 @@ ], castToNativeType: true, ), + 'anotherBooleanParameter' => new QueryParameter( + default: true, + ), ], provider: [self::class, 'provide'], ), @@ -38,12 +41,17 @@ )] class BooleanQueryParameter { - public function __construct(public bool $booleanParameter) - { + public function __construct( + public bool $booleanParameter, + public bool $anotherBooleanParameter, + ) { } public static function provide(Operation $operation): self { - return new self($operation->getParameters()->get('booleanParameter')->getValue()); + return new self( + $operation->getParameters()->get('booleanParameter')->getValue(), + $operation->getParameters()->get('anotherBooleanParameter')->getValue(), + ); } } diff --git a/tests/Fixtures/TestBundle/ApiResource/WithParameter.php b/tests/Fixtures/TestBundle/ApiResource/WithParameter.php index 02cd55d884f..7f5efb457e2 100644 --- a/tests/Fixtures/TestBundle/ApiResource/WithParameter.php +++ b/tests/Fixtures/TestBundle/ApiResource/WithParameter.php @@ -38,6 +38,7 @@ use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints\Country; +use Webmozart\Assert\Assert as Assertion; #[Get( uriTemplate: 'with_parameters/{id}{._format}', @@ -281,7 +282,25 @@ ], provider: [self::class, 'collectionProvider'], )] - +#[GetCollection( + uriTemplate: 'parameter_defaults', + parameters: [ + 'false_as_default' => new QueryParameter( + default: false, + ), + 'true_as_default' => new QueryParameter( + default: true, + ), + 'foo_as_default' => new QueryParameter( + default: 'foo', + ), + 'required_with_a_default' => new QueryParameter( + required: true, + default: 'bar' + ), + ], + provider: [self::class, 'checkParameterDefaults'], +)] #[QueryParameter(key: 'everywhere')] class WithParameter { @@ -372,4 +391,14 @@ public static function provideDummyFromParameter(Operation $operation, array $ur { return $operation->getParameters()->get('dummy')->getValue(); } + + public static function checkParameterDefaults(Operation $operation, array $uriVariables = [], array $context = []): JsonResponse + { + Assertion::false($operation->getParameters()->get('false_as_default')->getValue()); + Assertion::true($operation->getParameters()->get('true_as_default')->getValue()); + Assertion::same($operation->getParameters()->get('foo_as_default')->getValue(), 'foo'); + Assertion::same($operation->getParameters()->get('required_with_a_default')->getValue(), 'bar'); + + return new JsonResponse([]); + } } diff --git a/tests/Functional/Parameters/ParameterTest.php b/tests/Functional/Parameters/ParameterTest.php index 122b195df1f..2290cb475ee 100644 --- a/tests/Functional/Parameters/ParameterTest.php +++ b/tests/Functional/Parameters/ParameterTest.php @@ -215,4 +215,10 @@ public static function provideCountriesValues(): iterable yield 'valid country' => ['country=FR', 200]; yield 'array of countries' => ['country[]=FR', 200]; } + + public function testDefaultValues(): void + { + self::createClient()->request('GET', 'parameter_defaults'); + $this->assertResponseIsSuccessful(); + } }