Skip to content

Commit 604e56d

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

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

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: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
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;
@@ -658,6 +659,78 @@ public function testHeaderParameterFromPropertyAttributePropertiesHasMultipleInc
658659
$this->assertSame(['authToken'], $authParam->getProperties());
659660
}
660661

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

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

0 commit comments

Comments
 (0)