Skip to content

Commit 5bd3b33

Browse files
authored
feat(metadata): accept operation class names in parameter operations (#8624)
1 parent ea207ab commit 5bd3b33

3 files changed

Lines changed: 91 additions & 4 deletions

File tree

‎src/Metadata/Parameter.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ abstract class Parameter
4242
* @param ?bool $castToNativeType whether API Platform should cast your parameter to the nativeType declared
4343
* @param ?callable(mixed): mixed $castFn the closure used to cast your parameter, this gets called only when $castToNativeType is set
4444
* @param ?string $filterClass the class to use when resolving filter properties (from stateOptions)
45-
* @param list<Operation>|null $operations a list of operations this parameter applies to (if null, applies to all operations)
45+
* @param list<Operation|class-string<Operation>>|null $operations a list of operations (instances or class names) this parameter applies to (if null, applies to all operations)
4646
*
4747
* @phpstan-param array<string, mixed>|null $schema
4848
*
@@ -414,15 +414,15 @@ public function withFilterClass(?string $filterClass): self
414414
}
415415

416416
/**
417-
* @return list<Operation>|null
417+
* @return list<Operation|class-string<Operation>>|null
418418
*/
419419
public function getOperations(): ?array
420420
{
421421
return $this->operations;
422422
}
423423

424424
/**
425-
* @param list<Operation>|null $operations
425+
* @param list<Operation|class-string<Operation>>|null $operations
426426
*/
427427
public function withOperations(?array $operations): self
428428
{

‎src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ private function createParametersFromAttributes(Operation $operation, array $dec
533533
$propertyName = $reflectionProperty->getName();
534534

535535
if (null !== ($parameterOperations = $parameter->getOperations())) {
536-
$parameterOperationClasses = array_map(static fn ($parameterOperation) => $parameterOperation::class, $parameterOperations);
536+
$parameterOperationClasses = array_map(static fn (Operation|string $parameterOperation): string => \is_string($parameterOperation) ? $parameterOperation : $parameterOperation::class, $parameterOperations);
537537

538538
foreach ($parameterOperationClasses as $parameterOperationClass) {
539539
if (!\in_array($parameterOperationClass, $declaredOperationClasses, true)) {

‎src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,67 @@ public function testQueryParameterOnPropertiesThrowsExceptionWhenGraphQlOperatio
985985
$parameterFactory->create(QueryParameterOnPropertiesWithUndeclaredGraphQlOperation::class);
986986
}
987987

988+
public function testParameterOnPropertiesWithOperationClassStrings(): void
989+
{
990+
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
991+
$nameCollection->method('create')->willReturn(new PropertyNameCollection(['name', 'authToken']));
992+
993+
$propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class);
994+
$propertyMetadata->method('create')->willReturn(
995+
new ApiProperty(readable: true),
996+
);
997+
998+
$filterLocator = $this->createStub(ContainerInterface::class);
999+
$filterLocator->method('has')->willReturn(false);
1000+
1001+
$parameterFactory = new ParameterResourceMetadataCollectionFactory(
1002+
$nameCollection,
1003+
$propertyMetadata,
1004+
new AttributesResourceMetadataCollectionFactory(),
1005+
$filterLocator
1006+
);
1007+
1008+
$resourceMetadataCollection = $parameterFactory->create(ParameterOnPropertiesWithOperationClassStrings::class);
1009+
$operations = array_values(iterator_to_array($resourceMetadataCollection[0]->getOperations()));
1010+
1011+
$this->assertCount(2, $operations);
1012+
1013+
[$collectionOperation, $getOperation] = $operations;
1014+
$this->assertInstanceOf(GetCollection::class, $collectionOperation);
1015+
$this->assertTrue($collectionOperation->getParameters()->has('search', QueryParameter::class));
1016+
$this->assertTrue($collectionOperation->getParameters()->has('X-Authorization', HeaderParameter::class));
1017+
1018+
$this->assertInstanceOf(Get::class, $getOperation);
1019+
$this->assertFalse($getOperation->getParameters()->has('search', QueryParameter::class));
1020+
$this->assertTrue($getOperation->getParameters()->has('X-Authorization', HeaderParameter::class));
1021+
}
1022+
1023+
public function testParameterOnPropertiesThrowsExceptionWhenOperationClassStringIsNotDeclared(): void
1024+
{
1025+
$this->expectException(RuntimeException::class);
1026+
$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));
1027+
1028+
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
1029+
$nameCollection->method('create')->willReturn(new PropertyNameCollection(['name']));
1030+
1031+
$propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class);
1032+
$propertyMetadata->method('create')->willReturn(
1033+
new ApiProperty(readable: true),
1034+
);
1035+
1036+
$filterLocator = $this->createStub(ContainerInterface::class);
1037+
$filterLocator->method('has')->willReturn(false);
1038+
1039+
$parameterFactory = new ParameterResourceMetadataCollectionFactory(
1040+
$nameCollection,
1041+
$propertyMetadata,
1042+
new AttributesResourceMetadataCollectionFactory(),
1043+
$filterLocator
1044+
);
1045+
1046+
$parameterFactory->create(ParameterOnPropertiesWithUndeclaredOperationClassString::class);
1047+
}
1048+
9881049
public function testNestedPropertyWithNameConverter(): void
9891050
{
9901051
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
@@ -1544,3 +1605,29 @@ class QueryParameterOnPropertiesWithUndeclaredGraphQlOperation
15441605
#[QueryParameter(key: 'search', description: 'Search by name', operations: [new Mutation(name: 'create')])]
15451606
public string $name = '';
15461607
}
1608+
1609+
#[ApiResource(
1610+
operations: [
1611+
new GetCollection(),
1612+
new Get(),
1613+
]
1614+
)]
1615+
class ParameterOnPropertiesWithOperationClassStrings
1616+
{
1617+
#[QueryParameter(key: 'search', description: 'Search by name', operations: [GetCollection::class])]
1618+
public string $name = '';
1619+
1620+
#[HeaderParameter(key: 'X-Authorization', description: 'Authorization header', operations: [Get::class, new GetCollection()])]
1621+
public string $authToken = '';
1622+
}
1623+
1624+
#[ApiResource(
1625+
operations: [
1626+
new GetCollection(),
1627+
]
1628+
)]
1629+
class ParameterOnPropertiesWithUndeclaredOperationClassString
1630+
{
1631+
#[QueryParameter(key: 'search', description: 'Search by name', operations: [Patch::class])]
1632+
public string $name = '';
1633+
}

0 commit comments

Comments
 (0)