diff --git a/src/Metadata/Parameter.php b/src/Metadata/Parameter.php index fff2a2575c..ac0026b63e 100644 --- a/src/Metadata/Parameter.php +++ b/src/Metadata/Parameter.php @@ -42,7 +42,7 @@ abstract class Parameter * @param ?bool $castToNativeType whether API Platform should cast your parameter to the nativeType declared * @param ?callable(mixed): mixed $castFn the closure used to cast your parameter, this gets called only when $castToNativeType is set * @param ?string $filterClass the class to use when resolving filter properties (from stateOptions) - * @param list|null $operations a list of operations this parameter applies to (if null, applies to all operations) + * @param list>|null $operations a list of operations (instances or class names) this parameter applies to (if null, applies to all operations) * * @phpstan-param array|null $schema * @@ -414,7 +414,7 @@ public function withFilterClass(?string $filterClass): self } /** - * @return list|null + * @return list>|null */ public function getOperations(): ?array { @@ -422,7 +422,7 @@ public function getOperations(): ?array } /** - * @param list|null $operations + * @param list>|null $operations */ public function withOperations(?array $operations): self { diff --git a/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php index 57c930daa6..f365ba512a 100644 --- a/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php @@ -533,7 +533,7 @@ private function createParametersFromAttributes(Operation $operation, array $dec $propertyName = $reflectionProperty->getName(); if (null !== ($parameterOperations = $parameter->getOperations())) { - $parameterOperationClasses = array_map(static fn ($parameterOperation) => $parameterOperation::class, $parameterOperations); + $parameterOperationClasses = array_map(static fn (Operation|string $parameterOperation): string => \is_string($parameterOperation) ? $parameterOperation : $parameterOperation::class, $parameterOperations); foreach ($parameterOperationClasses as $parameterOperationClass) { if (!\in_array($parameterOperationClass, $declaredOperationClasses, true)) { diff --git a/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php index c03f1085a3..4ba89cc49a 100644 --- a/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php @@ -985,6 +985,67 @@ public function testQueryParameterOnPropertiesThrowsExceptionWhenGraphQlOperatio $parameterFactory->create(QueryParameterOnPropertiesWithUndeclaredGraphQlOperation::class); } + public function testParameterOnPropertiesWithOperationClassStrings(): void + { + $nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class); + $nameCollection->method('create')->willReturn(new PropertyNameCollection(['name', 'authToken'])); + + $propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class); + $propertyMetadata->method('create')->willReturn( + new ApiProperty(readable: true), + ); + + $filterLocator = $this->createStub(ContainerInterface::class); + $filterLocator->method('has')->willReturn(false); + + $parameterFactory = new ParameterResourceMetadataCollectionFactory( + $nameCollection, + $propertyMetadata, + new AttributesResourceMetadataCollectionFactory(), + $filterLocator + ); + + $resourceMetadataCollection = $parameterFactory->create(ParameterOnPropertiesWithOperationClassStrings::class); + $operations = array_values(iterator_to_array($resourceMetadataCollection[0]->getOperations())); + + $this->assertCount(2, $operations); + + [$collectionOperation, $getOperation] = $operations; + $this->assertInstanceOf(GetCollection::class, $collectionOperation); + $this->assertTrue($collectionOperation->getParameters()->has('search', QueryParameter::class)); + $this->assertTrue($collectionOperation->getParameters()->has('X-Authorization', HeaderParameter::class)); + + $this->assertInstanceOf(Get::class, $getOperation); + $this->assertFalse($getOperation->getParameters()->has('search', QueryParameter::class)); + $this->assertTrue($getOperation->getParameters()->has('X-Authorization', HeaderParameter::class)); + } + + public function testParameterOnPropertiesThrowsExceptionWhenOperationClassStringIsNotDeclared(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage(\sprintf('Parameter attribute on property "name" is restricted to the operation "%s" which is not declared on the resource "%s".', Patch::class, ParameterOnPropertiesWithUndeclaredOperationClassString::class)); + + $nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class); + $nameCollection->method('create')->willReturn(new PropertyNameCollection(['name'])); + + $propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class); + $propertyMetadata->method('create')->willReturn( + new ApiProperty(readable: true), + ); + + $filterLocator = $this->createStub(ContainerInterface::class); + $filterLocator->method('has')->willReturn(false); + + $parameterFactory = new ParameterResourceMetadataCollectionFactory( + $nameCollection, + $propertyMetadata, + new AttributesResourceMetadataCollectionFactory(), + $filterLocator + ); + + $parameterFactory->create(ParameterOnPropertiesWithUndeclaredOperationClassString::class); + } + public function testNestedPropertyWithNameConverter(): void { $nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class); @@ -1544,3 +1605,29 @@ class QueryParameterOnPropertiesWithUndeclaredGraphQlOperation #[QueryParameter(key: 'search', description: 'Search by name', operations: [new Mutation(name: 'create')])] public string $name = ''; } + +#[ApiResource( + operations: [ + new GetCollection(), + new Get(), + ] +)] +class ParameterOnPropertiesWithOperationClassStrings +{ + #[QueryParameter(key: 'search', description: 'Search by name', operations: [GetCollection::class])] + public string $name = ''; + + #[HeaderParameter(key: 'X-Authorization', description: 'Authorization header', operations: [Get::class, new GetCollection()])] + public string $authToken = ''; +} + +#[ApiResource( + operations: [ + new GetCollection(), + ] +)] +class ParameterOnPropertiesWithUndeclaredOperationClassString +{ + #[QueryParameter(key: 'search', description: 'Search by name', operations: [Patch::class])] + public string $name = ''; +}