Skip to content

Commit 1d22236

Browse files
committed
feat: allow Parameter attributes on properties
1 parent effc99e commit 1d22236

4 files changed

Lines changed: 207 additions & 1 deletion

File tree

src/Metadata/QueryParameter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace ApiPlatform\Metadata;
1515

16-
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
16+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE | \Attribute::TARGET_PROPERTY)]
1717
class QueryParameter extends Parameter implements QueryParameterInterface
1818
{
1919
}

src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\Metadata\Exception\RuntimeException;
1919
use ApiPlatform\Metadata\FilterInterface;
2020
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
21+
use ApiPlatform\Metadata\Metadata;
2122
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
2223
use ApiPlatform\Metadata\Operation;
2324
use ApiPlatform\Metadata\Parameter;
@@ -231,6 +232,10 @@ private function getDefaultParameters(Operation $operation, string $resourceClas
231232
$propertyNames = $properties = [];
232233
$parameters = $operation->getParameters() ?? new Parameters();
233234

235+
foreach ($this->createParametersFromAttributes($operation) as $key => $parameter) {
236+
$parameters->add($key, $parameter);
237+
}
238+
234239
// First loop we look for the :property placeholder and replace its key
235240
foreach ($parameters as $key => $parameter) {
236241
if (!str_contains($key, ':property')) {
@@ -466,4 +471,27 @@ private function getFilterInstance(object|string|null $filter): ?object
466471

467472
return $this->filterLocator->get($filter);
468473
}
474+
475+
private function createParametersFromAttributes(Metadata $operation): Parameters
476+
{
477+
$parameters = new Parameters();
478+
479+
$reflectionClass = new \ReflectionClass($operation->getClass());
480+
481+
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
482+
foreach ($reflectionProperty->getAttributes(Parameter::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
483+
$parameter = $attribute->newInstance();
484+
485+
$key = $parameter->getKey() ?? $reflectionProperty->getName();
486+
487+
if (null === $parameter->getProperty()) {
488+
$parameter = $parameter->withProperty($reflectionProperty->getName());
489+
}
490+
491+
$parameters->add($key, $parameter);
492+
}
493+
}
494+
495+
return $parameters;
496+
}
469497
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Metadata\GetCollection;
20+
use ApiPlatform\Metadata\Parameters;
21+
use ApiPlatform\Metadata\QueryParameter;
22+
use Doctrine\ORM\Mapping as ORM;
23+
24+
#[ORM\Entity]
25+
#[ApiResource(
26+
uriTemplate: 'parameter_on_properties',
27+
// normalizationContext: ['hydra_prefix' => false],
28+
// provider: [self::class, 'provide'],
29+
operations: [
30+
new GetCollection(
31+
// parameters: [
32+
// new QueryParameter(
33+
// key: 'qname',
34+
// filter: new PartialSearchFilter(),
35+
// property: 'name',
36+
// ),
37+
// // new QueryParameter(
38+
// // key: 'de',
39+
// // filter: new PartialSearchFilter(),
40+
// // property: 'description',
41+
// // ),
42+
// ],
43+
),
44+
new Get(),
45+
]
46+
)]
47+
class ParameterOnProperties
48+
{
49+
#[ORM\Id]
50+
#[ORM\GeneratedValue]
51+
#[ORM\Column(type: 'integer')]
52+
private ?int $id = null;
53+
54+
#[ORM\Column(type: 'string')]
55+
#[QueryParameter(key: 'qname', filter: new PartialSearchFilter())]
56+
private string $name = '';
57+
58+
#[ORM\Column(type: 'string', nullable: true)]
59+
private ?string $description = null;
60+
61+
public function __construct(string $name = '', ?string $description = null)
62+
{
63+
$this->name = $name;
64+
$this->description = $description;
65+
}
66+
67+
public function getId(): ?int
68+
{
69+
return $this->id;
70+
}
71+
72+
public function getName(): string
73+
{
74+
return $this->name;
75+
}
76+
77+
public function setName(string $name): self
78+
{
79+
$this->name = $name;
80+
81+
return $this;
82+
}
83+
84+
public function getDescription(): ?string
85+
{
86+
return $this->description;
87+
}
88+
89+
public function setDescription(?string $description): self
90+
{
91+
$this->description = $description;
92+
93+
return $this;
94+
}
95+
96+
// public static function provide(): array
97+
// {
98+
// return [
99+
// new self('foo', 'bar'),
100+
// new self('baz', 'qux'),
101+
// new self('qoox', 'corge'),
102+
// ];
103+
// }
104+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Functional\Parameters;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ParameterOnProperties;
18+
use ApiPlatform\Tests\RecreateSchemaTrait;
19+
use ApiPlatform\Tests\SetupClassResourcesTrait;
20+
21+
final class ParameterOnPropertiesTest extends ApiTestCase
22+
{
23+
use RecreateSchemaTrait;
24+
use SetupClassResourcesTrait;
25+
26+
protected static ?bool $alwaysBootKernel = false;
27+
28+
/**
29+
* @return class-string[]
30+
*/
31+
public static function getResources(): array
32+
{
33+
return [ParameterOnProperties::class];
34+
}
35+
36+
/**
37+
* @throws \Throwable
38+
*/
39+
protected function setUp(): void
40+
{
41+
$this->recreateSchema([ParameterOnProperties::class]);
42+
$this->loadFixtures();
43+
}
44+
45+
public function testQueryParameterOnProperty(): void
46+
{
47+
$response = self::createClient()->request('GET', 'parameter_on_properties?qname=oo');
48+
$this->assertResponseIsSuccessful();
49+
50+
$responseData = $response->toArray();
51+
52+
$this->assertArrayHasKey('hydra:member', $responseData);
53+
$members = $responseData['hydra:member'];
54+
55+
$this->assertCount(2, $members);
56+
$this->assertSame('foo', $members[0]['name']);
57+
$this->assertSame('qoox', $members[1]['name']);
58+
}
59+
60+
/**
61+
* @throws \Throwable
62+
* @throws MongoDBException
63+
*/
64+
private function loadFixtures(): void
65+
{
66+
$manager = $this->getManager();
67+
68+
$manager->persist(new ParameterOnProperties('foo', 'bar'));
69+
$manager->persist(new ParameterOnProperties('baz', 'qux'));
70+
$manager->persist(new ParameterOnProperties('qoox', 'corge'));
71+
72+
$manager->flush();
73+
}
74+
}

0 commit comments

Comments
 (0)