Skip to content

Commit b6ba5e3

Browse files
committed
feat: allow restricting operations for parameter attributes on properties
1 parent 7d95614 commit b6ba5e3

3 files changed

Lines changed: 136 additions & 0 deletions

File tree

src/Metadata/Parameter.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +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)
4546
*
4647
* @phpstan-param array<string, mixed>|null $schema
4748
*
@@ -70,6 +71,7 @@ public function __construct(
7071
protected mixed $castFn = null,
7172
protected mixed $default = null,
7273
protected ?string $filterClass = null,
74+
protected ?array $operations = null,
7375
) {
7476
}
7577

@@ -410,4 +412,23 @@ public function withFilterClass(?string $filterClass): self
410412

411413
return $self;
412414
}
415+
416+
/**
417+
* @return list<Operation>|null
418+
*/
419+
public function getOperations(): ?array
420+
{
421+
return $this->operations;
422+
}
423+
424+
/**
425+
* @param list<Operation>|null $operations
426+
*/
427+
public function withOperations(?array $operations): self
428+
{
429+
$self = clone $this;
430+
$self->operations = $operations;
431+
432+
return $self;
433+
}
413434
}

src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,17 @@ private function createParametersFromAttributes(Operation $operation): Parameter
486486
foreach ($reflectionProperty->getAttributes(Parameter::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
487487
$parameter = $attribute->newInstance();
488488

489+
if (
490+
null !== ($parameterOperations = $parameter->getOperations())
491+
&& !\in_array(
492+
$operation::class,
493+
array_map(static fn ($parameterOperation) => $parameterOperation::class, $parameterOperations),
494+
true
495+
)
496+
) {
497+
continue;
498+
}
499+
489500
$propertyName = $reflectionProperty->getName();
490501
$key = $parameter->getKey() ?? $propertyName;
491502

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

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
use ApiPlatform\Metadata\ApiResource;
1818
use ApiPlatform\Metadata\Exception\RuntimeException;
1919
use ApiPlatform\Metadata\FilterInterface;
20+
use ApiPlatform\Metadata\Get;
2021
use ApiPlatform\Metadata\GetCollection;
2122
use ApiPlatform\Metadata\HeaderParameter;
2223
use ApiPlatform\Metadata\Parameters;
24+
use ApiPlatform\Metadata\Patch;
2325
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2426
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2527
use ApiPlatform\Metadata\Property\PropertyNameCollection;
@@ -658,6 +660,78 @@ public function testHeaderParameterFromPropertyAttributePropertiesHasMultipleInc
658660
$this->assertSame(['authToken'], $authParam->getProperties());
659661
}
660662

663+
public function testQueryParameterOnPropertiesWithOperations(): void
664+
{
665+
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
666+
$nameCollection->method('create')->willReturn(new PropertyNameCollection(['id', 'name']));
667+
668+
$propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class);
669+
$propertyMetadata->method('create')->willReturn(
670+
new ApiProperty(readable: true),
671+
);
672+
673+
$filterLocator = $this->createStub(ContainerInterface::class);
674+
$filterLocator->method('has')->willReturn(false);
675+
676+
$parameterFactory = new ParameterResourceMetadataCollectionFactory(
677+
$nameCollection,
678+
$propertyMetadata,
679+
new AttributesResourceMetadataCollectionFactory(),
680+
$filterLocator
681+
);
682+
683+
$resourceMetadataCollection = $parameterFactory->create(QueryParameterOnPropertiesWithOperations::class);
684+
$operations = array_values(iterator_to_array($resourceMetadataCollection[0]->getOperations()));
685+
686+
$collectionOperation = $operations[0];
687+
$this->assertInstanceOf(GetCollection::class, $collectionOperation);
688+
$collectionParameters = $collectionOperation->getParameters();
689+
$this->assertTrue($collectionParameters->has('search'));
690+
$this->assertFalse($collectionParameters->has('filter_id'));
691+
692+
$getOperation = $operations[1];
693+
$this->assertInstanceOf(Get::class, $getOperation);
694+
$getParameters = $getOperation->getParameters();
695+
$this->assertTrue($getParameters->has('search'));
696+
$this->assertTrue($getParameters->has('filter_id'));
697+
}
698+
699+
public function testHeaderParameterOnPropertiesWithOperations(): void
700+
{
701+
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
702+
$nameCollection->method('create')->willReturn(new PropertyNameCollection(['id', 'authToken', 'apiKey']));
703+
704+
$propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class);
705+
$propertyMetadata->method('create')->willReturn(
706+
new ApiProperty(readable: true),
707+
);
708+
709+
$filterLocator = $this->createStub(ContainerInterface::class);
710+
$filterLocator->method('has')->willReturn(false);
711+
712+
$parameterFactory = new ParameterResourceMetadataCollectionFactory(
713+
$nameCollection,
714+
$propertyMetadata,
715+
new AttributesResourceMetadataCollectionFactory(),
716+
$filterLocator
717+
);
718+
719+
$resourceMetadataCollection = $parameterFactory->create(HeaderParameterOnPropertiesWithOperations::class);
720+
$operations = array_values(iterator_to_array($resourceMetadataCollection[0]->getOperations()));
721+
722+
$collectionOperation = $operations[0];
723+
$this->assertInstanceOf(GetCollection::class, $collectionOperation);
724+
$collectionParameters = $collectionOperation->getParameters();
725+
$this->assertFalse($collectionParameters->has('X-Authorization', HeaderParameter::class));
726+
$this->assertTrue($collectionParameters->has('X-API-Key', HeaderParameter::class));
727+
728+
$getOperation = $operations[1];
729+
$this->assertInstanceOf(Get::class, $getOperation);
730+
$getParameters = $getOperation->getParameters();
731+
$this->assertTrue($getParameters->has('X-Authorization', HeaderParameter::class));
732+
$this->assertTrue($getParameters->has('X-API-Key', HeaderParameter::class));
733+
}
734+
661735
public function testNestedPropertyWithNameConverter(): void
662736
{
663737
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
@@ -1027,3 +1101,33 @@ class HeaderParameterOnPropertiesMismatchMultiplePropertiesException
10271101

10281102
public string $token2 = '';
10291103
}
1104+
1105+
#[ApiResource(
1106+
operations: [
1107+
new GetCollection(),
1108+
new Get(),
1109+
]
1110+
)]
1111+
class QueryParameterOnPropertiesWithOperations
1112+
{
1113+
#[QueryParameter(key: 'search', description: 'Search by name', operations: [new GetCollection(), new Get()])]
1114+
public string $name = '';
1115+
1116+
#[QueryParameter(key: 'filter_id', description: 'Filter by ID', operations: [new Get(), new Patch()])]
1117+
public int $id = 0;
1118+
}
1119+
1120+
#[ApiResource(
1121+
operations: [
1122+
new GetCollection(),
1123+
new Get(),
1124+
]
1125+
)]
1126+
class HeaderParameterOnPropertiesWithOperations
1127+
{
1128+
#[HeaderParameter(key: 'X-Authorization', description: 'Authorization header', operations: [new Get()])]
1129+
public string $authToken = '';
1130+
1131+
#[HeaderParameter(key: 'X-API-Key', description: 'API key header', operations: [new GetCollection(), new Get()])]
1132+
public string $apiKey = '';
1133+
}

0 commit comments

Comments
 (0)