Skip to content

Commit 6b3ee49

Browse files
authored
feat(attribute): migration from annotation to PHP8 attributes (#11)
* feat(attribute): migration from annotation * feat(arguments): updated readme and added named arguments * feat(rector): applied rector fix * fix(test): fix typo
1 parent 3edb6a6 commit 6b3ee49

9 files changed

Lines changed: 180 additions & 168 deletions

README.md

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ bedrock_rate_limit:
2626
limit_by_route: true|false # false by default
2727
display_headers: true|false # false by default
2828
```
29-
By default, the limitation is common to all routes annotated `@RateLimit()`.
30-
For example, if you keep the default configuration and you configure the `@RateLimit()` annotation in 2 routes. Limit will shared between this 2 routes, if user consume all authorized calls on the first route, the second route couldn't be called.
29+
By default, the limitation is common to all routes annotated `#[RateLimit]`.
30+
For example, if you keep the default configuration and you configure the `#[RateLimit]` attribute in 2 routes. Limit will shared between this 2 routes, if user consume all authorized calls on the first route, the second route couldn't be called.
3131
If you swicth `limit_by_route` to true, users will be allowed to reach the limit on each route annotated.
3232

33-
`@GraphQLRateLimit()`annotation allows you to rate limit by graphQL query or mutation.
34-
/!\ To use this annotation, you will need to install suggested package.
33+
`#[GraphQLRateLimit]`attribute allows you to rate limit by graphQL query or mutation.
34+
/!\ To use this attribute, you will need to install suggested package.
3535

3636
If you switch `display_headers` to true, 3 headers will be added `x-rate-limit`, `x-rate-limit-hits`, `x-rate-limit-untils` to your responses. This can be usefull to debug your limitations.
3737
`display_headers` is used to display a verbose return if limit is reached.
@@ -76,33 +76,26 @@ You can also create your own rate limit modifier by implementing `RateLimitModif
7676

7777
### Configure your routes
7878

79-
#### With annotations
79+
#### With attribute
8080

81-
Add the `@RateLimit()` annotation to your controller methods (by default, the limit will be 1000 requests per minute).
82-
This annotation accepts parameters to customize the rate limit. The following example shows how to limit requests on a route at the rate of 10 requests max every 2 minutes.
81+
Add the `#[RateLimit]` attribute to your controller methods (by default, the limit will be 1000 requests per minute).
82+
This attribute accepts parameters to customize the rate limit. The following example shows how to limit requests on a route at the rate of 10 requests max every 2 minutes.
8383
:warning: This customization only works if the `limit_by_route` parameter is `true`
8484

8585
```php
86-
/**
87-
* @RateLimit(
88-
* limit=10,
89-
* period=120
90-
* )
91-
*/
86+
#[RateLimit(limit: 10, period: 120)]
9287
```
9388

94-
To rate limit your graphQL API, add the `@GraphQLRateLimit()` annotation to your graphQL controller.
95-
This annotation requires a list of endpoints and accepts parameters to customize the rate limit. The following example shows how to limit requests on an endpoint at the rate of 10 requests max every 2 minutes and on default limitations.
89+
To rate limit your graphQL API, add the `#[GraphQlRateLimit]` attribute to your graphQL controller.
90+
This attribute requires a list of endpoints and accepts parameters to customize the rate limit. The following example shows how to limit requests on an endpoint at the rate of 10 requests max every 2 minutes and on default limitations.
9691

9792
```php
98-
/**
99-
* @GraphQLRateLimit(
100-
* endpoints={
101-
* {"endpoint"="GetMyQuery", "limit"=10, "period"=120},
102-
* {"endpoint"="EditMyMutation"},
103-
* }
104-
* )
105-
*/
93+
#[GraphQlRateLimit(
94+
endpoints: [
95+
[ 'endpoint' => 'GetMyQuery', 'limit' => 10, 'period' => 12],
96+
[ 'endpoint' => 'EditMyMutation'],
97+
]
98+
)]
10699
```
107100

108101
#### In YAML
@@ -122,3 +115,23 @@ bedrock_rate_limit:
122115
post_foobar:
123116
period: 10
124117
```
118+
119+
#### Migration to php8
120+
121+
If you use rector you can use those rules to automatically migrate annotation to attributes :
122+
123+
```php
124+
$rectorConfig->ruleWithConfiguration(
125+
\Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class,
126+
[
127+
new \Rector\Php80\ValueObject\AnnotationToAttribute(
128+
'Bedrock\Bundle\RateLimitBundle\Annotation\GraphQLRateLimit',
129+
\Bedrock\Bundle\RateLimitBundle\Attribute\GraphQLRateLimit::class
130+
),
131+
new \Rector\Php80\ValueObject\AnnotationToAttribute(
132+
'Bedrock\Bundle\RateLimitBundle\Annotation\RateLimit',
133+
\Bedrock\Bundle\RateLimitBundle\Attribute\RateLimit::class
134+
)
135+
],
136+
);
137+
```

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
"require": {
1919
"php": ">=8.0",
2020
"ext-json": "*",
21-
"doctrine/annotations": "^1.10.0",
2221
"symfony/config": "^6.0",
2322
"symfony/dependency-injection": "^6.0",
2423
"symfony/event-dispatcher": "^6.0",
@@ -35,7 +34,7 @@
3534
"rector/rector": "^0.12.22"
3635
},
3736
"suggest": {
38-
"webonyx/graphql-php": "Needed to support @GraphQLRateLimit annotation"
37+
"webonyx/graphql-php": "Needed to support @GraphQLRateLimit attribute"
3938
},
4039
"autoload": {
4140
"psr-4": {
Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@
22

33
declare(strict_types=1);
44

5-
namespace Bedrock\Bundle\RateLimitBundle\Annotation;
5+
namespace Bedrock\Bundle\RateLimitBundle\Attribute;
66

77
use Bedrock\Bundle\RateLimitBundle\Model\GraphQLEndpointConfiguration;
88
use Symfony\Component\OptionsResolver\OptionsResolver;
99

10-
/**
11-
* @Annotation
12-
* @Target({"METHOD"})
13-
*/
10+
#[\Attribute(\Attribute::TARGET_METHOD)]
1411
final class GraphQLRateLimit
1512
{
1613
/** @var array<GraphQLEndpointConfiguration> */
1714
private array $endpointConfigurations;
1815

1916
/**
20-
* @param array<string, mixed> $args
17+
* @param array<array<string, string|int|null>> $endpoints
2118
*/
22-
public function __construct(array $args = [])
19+
public function __construct(array $endpoints = [])
2320
{
2421
$optionResolver = (new OptionsResolver())->setDefault('endpoints', function (OptionsResolver $endpointResolver) {
2522
$endpointResolver->setPrototype(true)
@@ -33,7 +30,7 @@ public function __construct(array $args = [])
3330
->setAllowedTypes('period', ['int', 'null']);
3431
});
3532

36-
$resolvedArgs = $optionResolver->resolve($args);
33+
$resolvedArgs = $optionResolver->resolve(['endpoints' => $endpoints]);
3734

3835
foreach ($resolvedArgs['endpoints'] as $endpoint) {
3936
$this->endpointConfigurations[] = new GraphQLEndpointConfiguration($endpoint['limit'], $endpoint['period'], $endpoint['endpoint']);
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,13 @@
22

33
declare(strict_types=1);
44

5-
namespace Bedrock\Bundle\RateLimitBundle\Annotation;
5+
namespace Bedrock\Bundle\RateLimitBundle\Attribute;
66

7-
/**
8-
* @Annotation
9-
* @Target({"METHOD"})
10-
*/
7+
#[\Attribute(\Attribute::TARGET_METHOD)]
118
final class RateLimit
129
{
13-
private ?int $limit;
14-
private ?int $period;
15-
16-
/**
17-
* @param array<string, int> $args
18-
*/
19-
public function __construct(array $args = [])
10+
public function __construct(private ?int $limit = null, private ?int $period = null)
2011
{
21-
$this->limit = $args['limit'] ?? null;
22-
$this->period = $args['period'] ?? null;
2312
}
2413

2514
public function getLimit(): ?int

src/EventListener/ReadGraphQLRateLimitAnnotationListener.php renamed to src/EventListener/ReadGraphQLRateLimitAttributeListener.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,25 @@
22

33
namespace Bedrock\Bundle\RateLimitBundle\EventListener;
44

5-
use Bedrock\Bundle\RateLimitBundle\Annotation\GraphQLRateLimit as GraphQLRateLimitAnnotation;
5+
use Bedrock\Bundle\RateLimitBundle\Attribute\GraphQLRateLimit as GraphQLRateLimitAttribute;
66
use Bedrock\Bundle\RateLimitBundle\Model\RateLimit;
77
use Bedrock\Bundle\RateLimitBundle\RateLimitModifier\RateLimitModifierInterface;
8-
use Doctrine\Common\Annotations\Reader;
98
use GraphQL\Language\AST\OperationDefinitionNode;
109
use GraphQL\Language\Parser;
1110
use GraphQL\Language\Source;
1211
use Symfony\Component\DependencyInjection\ContainerInterface;
1312
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1413
use Symfony\Component\HttpKernel\Event\ControllerEvent;
1514

16-
class ReadGraphQLRateLimitAnnotationListener implements EventSubscriberInterface
15+
class ReadGraphQLRateLimitAttributeListener implements EventSubscriberInterface
1716
{
1817
/** @var iterable<RateLimitModifierInterface> */
1918
private iterable $rateLimitModifiers;
2019

2120
/**
2221
* @param RateLimitModifierInterface[] $rateLimitModifiers
2322
*/
24-
public function __construct(private ContainerInterface $container, private Reader $annotationReader, iterable $rateLimitModifiers, private int $limit, private int $period)
23+
public function __construct(private ContainerInterface $container, iterable $rateLimitModifiers, private int $limit, private int $period)
2524
{
2625
foreach ($rateLimitModifiers as $rateLimitModifier) {
2726
if (!($rateLimitModifier instanceof RateLimitModifierInterface)) {
@@ -51,19 +50,29 @@ public function onKernelController(ControllerEvent $event): void
5150
}
5251
}
5352
$reflection = new \ReflectionClass($controllerName);
54-
$annotation = $this->annotationReader->getMethodAnnotation($reflection->getMethod((string) ($methodName ?? '__invoke')), GraphQLRateLimitAnnotation::class);
53+
$attributes = $reflection->getMethod((string) ($methodName ?? '__invoke'))->getAttributes(GraphQLRateLimitAttribute::class);
5554

56-
if (!$annotation instanceof GraphQLRateLimitAnnotation) {
55+
if (count($attributes) > 1) {
56+
throw new \InvalidArgumentException('Unexpected value');
57+
}
58+
59+
/** @var ?\ReflectionAttribute $attribute */
60+
$attribute = array_shift($attributes);
61+
62+
if (null === $attribute) {
5763
return;
5864
}
5965

66+
/** @var GraphQLRateLimitAttribute $rateLimitAttribute */
67+
$rateLimitAttribute = $attribute->newInstance();
68+
6069
if (!class_exists(\GraphQL\Language\Parser::class)) {
61-
throw new \Exception('Run "composer require webonyx/graphql-php" to use @GraphQLRateLimit annotation.');
70+
throw new \Exception('Run "composer require webonyx/graphql-php" to use @GraphQLRateLimit attribute.');
6271
}
6372

6473
$endpoint = $this->extractQueryName($request->request->get('query'));
6574

66-
foreach ($annotation->getEndpointConfigurations() as $graphQLEndpointConfiguration) {
75+
foreach ($rateLimitAttribute->getEndpointConfigurations() as $graphQLEndpointConfiguration) {
6776
if ($endpoint === $graphQLEndpointConfiguration->getEndpoint()) {
6877
$rateLimit = new RateLimit(
6978
$graphQLEndpointConfiguration->getLimit() ?? $this->limit,

src/EventListener/ReadRateLimitAnnotationListener.php renamed to src/EventListener/ReadRateLimitAttributeListener.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@
44

55
namespace Bedrock\Bundle\RateLimitBundle\EventListener;
66

7-
use Bedrock\Bundle\RateLimitBundle\Annotation\RateLimit as RateLimitAnnotation;
7+
use Bedrock\Bundle\RateLimitBundle\Attribute\RateLimit as RateLimitAttribute;
88
use Bedrock\Bundle\RateLimitBundle\Model\RateLimit;
99
use Bedrock\Bundle\RateLimitBundle\RateLimitModifier\RateLimitModifierInterface;
10-
use Doctrine\Common\Annotations\Reader;
1110
use Symfony\Component\DependencyInjection\ContainerInterface;
1211
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1312
use Symfony\Component\HttpKernel\Event\ControllerEvent;
1413

15-
class ReadRateLimitAnnotationListener implements EventSubscriberInterface
14+
class ReadRateLimitAttributeListener implements EventSubscriberInterface
1615
{
1716
/** @var iterable<RateLimitModifierInterface> */
1817
private iterable $rateLimitModifiers;
1918

2019
/**
2120
* @param RateLimitModifierInterface[] $rateLimitModifiers
2221
*/
23-
public function __construct(private ContainerInterface $container, private Reader $annotationReader, iterable $rateLimitModifiers, private int $limit, private int $period, private bool $limitByRoute)
22+
public function __construct(private ContainerInterface $container, iterable $rateLimitModifiers, private int $limit, private int $period, private bool $limitByRoute)
2423
{
2524
foreach ($rateLimitModifiers as $rateLimitModifier) {
2625
if (!($rateLimitModifier instanceof RateLimitModifierInterface)) {
@@ -50,21 +49,31 @@ public function onKernelController(ControllerEvent $event): void
5049
}
5150
}
5251
$reflection = new \ReflectionClass($controllerName);
53-
$annotation = $this->annotationReader->getMethodAnnotation($reflection->getMethod((string) ($methodName ?? '__invoke')), RateLimitAnnotation::class);
52+
$attributes = $reflection->getMethod((string) ($methodName ?? '__invoke'))->getAttributes(RateLimitAttribute::class);
5453

55-
if (!$annotation instanceof RateLimitAnnotation) {
54+
if (count($attributes) > 1) {
55+
throw new \InvalidArgumentException('Unexpected value');
56+
}
57+
58+
/** @var ?\ReflectionAttribute $attribute */
59+
$attribute = array_shift($attributes);
60+
61+
if (null === $attribute) {
5662
return;
5763
}
5864

65+
/** @var RateLimitAttribute $rateLimitAttribute */
66+
$rateLimitAttribute = $attribute->newInstance();
67+
5968
$rateLimit = new RateLimit(
6069
$this->limit,
6170
$this->period
6271
);
6372

6473
if ($this->limitByRoute) {
6574
$rateLimit = new RateLimit(
66-
$annotation->getLimit() ?? $this->limit,
67-
$annotation->getPeriod() ?? $this->period
75+
$rateLimitAttribute->getLimit() ?? $this->limit,
76+
$rateLimitAttribute->getPeriod() ?? $this->period
6877
);
6978

7079
/** @var string $route */

src/Resources/config/services.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
Bedrock\Bundle\RateLimitBundle\:
77
resource: '../../../src/*'
88

9-
Bedrock\Bundle\RateLimitBundle\EventListener\ReadRateLimitAnnotationListener:
9+
Bedrock\Bundle\RateLimitBundle\EventListener\ReadRateLimitAttributeListener:
1010
arguments:
1111
$limit: '%bedrock_rate_limit.limit%'
1212
$period: '%bedrock_rate_limit.period%'
@@ -20,7 +20,7 @@ services:
2020
$rateLimitModifiers: !tagged rate_limit.modifiers
2121
$routes: '%bedrock_rate_limit.routes%'
2222

23-
Bedrock\Bundle\RateLimitBundle\EventListener\ReadGraphQLRateLimitAnnotationListener:
23+
Bedrock\Bundle\RateLimitBundle\EventListener\ReadGraphQLRateLimitAttributeListener:
2424
arguments:
2525
$limit: '%bedrock_rate_limit.limit%'
2626
$period: '%bedrock_rate_limit.period%'

0 commit comments

Comments
 (0)