Skip to content

Commit 1f419aa

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

File tree

10 files changed

+953
-2
lines changed

10 files changed

+953
-2
lines changed

src/Metadata/HeaderParameter.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 HeaderParameter extends Parameter implements HeaderParameterInterface
1818
{
1919
}

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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ private function getDefaultParameters(Operation $operation, string $resourceClas
231231
$propertyNames = $properties = [];
232232
$parameters = $operation->getParameters() ?? new Parameters();
233233

234+
foreach ($this->createParametersFromAttributes($operation) as $key => $parameter) {
235+
$parameters->add($key, $parameter);
236+
}
237+
234238
// First loop we look for the :property placeholder and replace its key
235239
foreach ($parameters as $key => $parameter) {
236240
if (!str_contains($key, ':property')) {
@@ -466,4 +470,38 @@ private function getFilterInstance(object|string|null $filter): ?object
466470

467471
return $this->filterLocator->get($filter);
468472
}
473+
474+
private function createParametersFromAttributes(Operation $operation): Parameters
475+
{
476+
$parameters = new Parameters();
477+
478+
if (null === $resourceClass = $operation->getClass()) {
479+
return $parameters;
480+
}
481+
482+
foreach ((new \ReflectionClass($resourceClass))->getProperties() as $reflectionProperty) {
483+
foreach ($reflectionProperty->getAttributes(Parameter::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
484+
$parameter = $attribute->newInstance();
485+
486+
$propertyName = $reflectionProperty->getName();
487+
$key = $parameter->getKey() ?? $propertyName;
488+
489+
if (null === $parameterPropertyName = $parameter->getProperty()) {
490+
$parameter = $parameter->withProperty($propertyName);
491+
} elseif ($parameterPropertyName !== $propertyName) {
492+
throw new RuntimeException(\sprintf('Parameter attribute on property "%s" must target itself or have no explicit property. Got "property: \'%s\'" instead.', $propertyName, $parameterPropertyName));
493+
}
494+
495+
if (null === ($parameterProperties = $parameter->getProperties()) || \in_array($propertyName, $parameterProperties, true)) {
496+
$parameter = $parameter->withProperties([$propertyName]);
497+
} elseif (!\in_array($propertyName, $parameterProperties, true)) {
498+
throw new RuntimeException(\sprintf('Parameter attribute on property "%s" must target itself or have no explicit properties. Got "properties: [%s]" instead.', $propertyName, implode(', ', $parameterProperties)));
499+
}
500+
501+
$parameters->add($key, $parameter);
502+
}
503+
}
504+
505+
return $parameters;
506+
}
469507
}

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

Lines changed: 490 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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\Document;
15+
16+
use ApiPlatform\Doctrine\Odm\Filter\PartialSearchFilter;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Metadata\GetCollection;
20+
use ApiPlatform\Metadata\QueryParameter;
21+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
22+
23+
#[ODM\Document]
24+
#[ApiResource(
25+
uriTemplate: 'parameter_on_properties',
26+
operations: [
27+
new GetCollection(),
28+
new Get(),
29+
]
30+
)]
31+
class ParameterOnProperties
32+
{
33+
#[ODM\Id]
34+
private ?string $id = null;
35+
36+
#[ODM\Field(type: 'string')]
37+
#[QueryParameter(key: 'qname', filter: new PartialSearchFilter())]
38+
private string $name = '';
39+
40+
#[ODM\Field(type: 'string', nullable: true)]
41+
private ?string $description = null;
42+
43+
public function __construct(string $name = '', ?string $description = null)
44+
{
45+
$this->name = $name;
46+
$this->description = $description;
47+
}
48+
49+
public function getId(): ?string
50+
{
51+
return $this->id;
52+
}
53+
54+
public function getName(): string
55+
{
56+
return $this->name;
57+
}
58+
59+
public function setName(string $name): self
60+
{
61+
$this->name = $name;
62+
63+
return $this;
64+
}
65+
66+
public function getDescription(): ?string
67+
{
68+
return $this->description;
69+
}
70+
71+
public function setDescription(?string $description): self
72+
{
73+
$this->description = $description;
74+
75+
return $this;
76+
}
77+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\Document;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use ApiPlatform\Metadata\HeaderParameter;
20+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
21+
22+
#[ODM\Document]
23+
#[ApiResource(
24+
uriTemplate: 'parameter_on_properties_with_header_parameter',
25+
operations: [
26+
new GetCollection(),
27+
new Get(),
28+
]
29+
)]
30+
class ParameterOnPropertiesWithHeaderParameter
31+
{
32+
#[ODM\Id]
33+
private ?string $id = null;
34+
35+
#[ODM\Field(type: 'string')]
36+
#[HeaderParameter(key: 'X-Authorization', description: 'Authorization header')]
37+
public string $authToken = '';
38+
39+
public function __construct(string $authToken = '')
40+
{
41+
$this->authToken = $authToken;
42+
}
43+
44+
public function getId(): ?string
45+
{
46+
return $this->id;
47+
}
48+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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\QueryParameter;
21+
use Doctrine\ORM\Mapping as ORM;
22+
23+
#[ORM\Entity]
24+
#[ApiResource(
25+
uriTemplate: 'parameter_on_properties',
26+
operations: [
27+
new GetCollection(),
28+
new Get(),
29+
]
30+
)]
31+
class ParameterOnProperties
32+
{
33+
#[ORM\Id]
34+
#[ORM\GeneratedValue]
35+
#[ORM\Column(type: 'integer')]
36+
private ?int $id = null;
37+
38+
#[ORM\Column(type: 'string')]
39+
#[QueryParameter(key: 'qname', filter: new PartialSearchFilter())]
40+
private string $name = '';
41+
42+
#[ORM\Column(type: 'string', nullable: true)]
43+
private ?string $description = null;
44+
45+
public function __construct(string $name = '', ?string $description = null)
46+
{
47+
$this->name = $name;
48+
$this->description = $description;
49+
}
50+
51+
public function getId(): ?int
52+
{
53+
return $this->id;
54+
}
55+
56+
public function getName(): string
57+
{
58+
return $this->name;
59+
}
60+
61+
public function setName(string $name): self
62+
{
63+
$this->name = $name;
64+
65+
return $this;
66+
}
67+
68+
public function getDescription(): ?string
69+
{
70+
return $this->description;
71+
}
72+
73+
public function setDescription(?string $description): self
74+
{
75+
$this->description = $description;
76+
77+
return $this;
78+
}
79+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use ApiPlatform\Metadata\HeaderParameter;
20+
use Doctrine\ORM\Mapping as ORM;
21+
22+
#[ORM\Entity]
23+
#[ApiResource(
24+
uriTemplate: 'parameter_on_properties_with_header_parameter',
25+
operations: [
26+
new GetCollection(),
27+
new Get(),
28+
]
29+
)]
30+
class ParameterOnPropertiesWithHeaderParameter
31+
{
32+
#[ORM\Id]
33+
#[ORM\GeneratedValue]
34+
#[ORM\Column(type: 'integer')]
35+
private ?int $id = null;
36+
37+
#[ORM\Column(type: 'string', name: 'auth_token')]
38+
#[HeaderParameter(key: 'X-Authorization', description: 'Authorization header')]
39+
public string $authToken = '';
40+
41+
public function __construct(string $authToken = '')
42+
{
43+
$this->authToken = $authToken;
44+
}
45+
46+
public function getId(): ?int
47+
{
48+
return $this->id;
49+
}
50+
}

0 commit comments

Comments
 (0)