Skip to content

Commit 0970d2f

Browse files
committed
feat(graphql): separate graphql annotation reader
1 parent c8b659c commit 0970d2f

5 files changed

Lines changed: 374 additions & 212 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
namespace Bedrock\Bundle\RateLimitBundle\EventListener;
4+
5+
use Bedrock\Bundle\RateLimitBundle\Annotation\GraphQLRateLimit as GraphQLRateLimitAnnotation;
6+
use Bedrock\Bundle\RateLimitBundle\Model\RateLimit;
7+
use Bedrock\Bundle\RateLimitBundle\RateLimitModifier\RateLimitModifierInterface;
8+
use Doctrine\Common\Annotations\Reader;
9+
use GraphQL\Language\AST\OperationDefinitionNode;
10+
use GraphQL\Language\Parser;
11+
use GraphQL\Language\Source;
12+
use Symfony\Component\DependencyInjection\ContainerInterface;
13+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
15+
16+
class ReadGraphQLRateLmitAnnotationListener implements EventSubscriberInterface
17+
{
18+
private Reader $annotationReader;
19+
/** @var iterable<RateLimitModifierInterface> */
20+
private $rateLimitModifiers;
21+
private int $limit;
22+
private int $period;
23+
private ContainerInterface $container;
24+
25+
/**
26+
* @param RateLimitModifierInterface[] $rateLimitModifiers
27+
*/
28+
public function __construct(ContainerInterface $container, Reader $annotationReader, iterable $rateLimitModifiers, int $limit, int $period)
29+
{
30+
foreach ($rateLimitModifiers as $rateLimitModifier) {
31+
if (!($rateLimitModifier instanceof RateLimitModifierInterface)) {
32+
throw new \InvalidArgumentException(('$rateLimitModifiers must be instance of '.RateLimitModifierInterface::class));
33+
}
34+
}
35+
36+
$this->annotationReader = $annotationReader;
37+
$this->rateLimitModifiers = $rateLimitModifiers;
38+
$this->limit = $limit;
39+
$this->period = $period;
40+
$this->container = $container;
41+
}
42+
43+
public function onKernelController(ControllerEvent $event): void
44+
{
45+
$request = $event->getRequest();
46+
// retrieve controller and method from request
47+
$controllerAttribute = $request->attributes->get('_controller', null);
48+
if (null === $controllerAttribute || !is_string($controllerAttribute)) {
49+
return;
50+
}
51+
// services alias can be used with 'service.alias:functionName' or 'service.alias::functionName'
52+
$controllerAttributeParts = explode(':', str_replace('::', ':', $controllerAttribute));
53+
$controllerName = $controllerAttributeParts[0] ?? '';
54+
$methodName = $controllerAttributeParts[1] ?? null;
55+
56+
if (!class_exists($controllerName)) {
57+
// If controller attribute is an alias instead of a class name
58+
if (null === ($controllerName = $this->container->get($controllerAttributeParts[0]))) {
59+
throw new \InvalidArgumentException('Parameter _controller from request : "'.$controllerAttribute.'" do not contains a valid class name');
60+
}
61+
}
62+
$reflection = new \ReflectionClass($controllerName);
63+
$annotation = $this->annotationReader->getMethodAnnotation($reflection->getMethod((string) ($methodName ?? '__invoke')), GraphQLRateLimitAnnotation::class);
64+
65+
if (!$annotation instanceof GraphQLRateLimitAnnotation) {
66+
return;
67+
}
68+
69+
if (!class_exists('GraphQL\Language\Parser')) {
70+
throw new \Exception('Run "composer require webonyx/graphql-php" to use @GraphQLRateLimit annotation.');
71+
}
72+
73+
$endpoint = $this->extractQueryName($request->request->get('query'));
74+
75+
foreach ($annotation->getEndpointConfigurations() as $graphQLEndpointConfiguration) {
76+
if ($endpoint === $graphQLEndpointConfiguration->getEndpoint()) {
77+
$rateLimit = new RateLimit(
78+
$graphQLEndpointConfiguration->getLimit() ?? $this->limit,
79+
$graphQLEndpointConfiguration->getPeriod() ?? $this->period
80+
);
81+
$rateLimit->varyHashOn('_graphql_endpoint', $endpoint);
82+
break;
83+
}
84+
}
85+
86+
if (!isset($rateLimit)) {
87+
return;
88+
}
89+
90+
foreach ($this->rateLimitModifiers as $hashKeyVarier) {
91+
if ($hashKeyVarier->support($request)) {
92+
$hashKeyVarier->modifyRateLimit($request, $rateLimit);
93+
}
94+
}
95+
$request->attributes->set('_rate_limit', $rateLimit);
96+
}
97+
98+
/**
99+
* @return array<string, string>
100+
*/
101+
public static function getSubscribedEvents(): array
102+
{
103+
return [
104+
ControllerEvent::class => 'onKernelController',
105+
];
106+
}
107+
108+
/**
109+
* @param Source|string $query
110+
*/
111+
public function extractQueryName($query): string
112+
{
113+
$parsedQuery = Parser::parse($query);
114+
/** @var OperationDefinitionNode $item */
115+
foreach ($parsedQuery->definitions->getIterator() as $item) {
116+
/* @phpstan-ignore-next-line */
117+
return (string) $item->selectionSet->selections[0]->name->value;
118+
}
119+
120+
throw new QueryExtractionException('Unable to extract query');
121+
}
122+
}

src/EventListener/ReadRateLimitAnnotationListener.php

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,10 @@
44

55
namespace Bedrock\Bundle\RateLimitBundle\EventListener;
66

7-
use Bedrock\Bundle\RateLimitBundle\Annotation\GraphQLRateLimit as GraphQLRateLimitAnnotation;
87
use Bedrock\Bundle\RateLimitBundle\Annotation\RateLimit as RateLimitAnnotation;
98
use Bedrock\Bundle\RateLimitBundle\Model\RateLimit;
109
use Bedrock\Bundle\RateLimitBundle\RateLimitModifier\RateLimitModifierInterface;
1110
use Doctrine\Common\Annotations\Reader;
12-
use GraphQL\Language\AST\OperationDefinitionNode;
13-
use GraphQL\Language\Parser;
14-
use GraphQL\Language\Source;
1511
use Symfony\Component\DependencyInjection\ContainerInterface;
1612
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1713
use Symfony\Component\HttpKernel\Event\ControllerEvent;
@@ -65,48 +61,24 @@ public function onKernelController(ControllerEvent $event): void
6561
}
6662
}
6763
$reflection = new \ReflectionClass($controllerName);
68-
$annotation = $this->annotationReader->getMethodAnnotation($reflection->getMethod((string) ($methodName ?? '__invoke')), RateLimitAnnotation::class) ?? $this->annotationReader->getMethodAnnotation($reflection->getMethod((string) ($methodName ?? '__invoke')), GraphQLRateLimitAnnotation::class);
64+
$annotation = $this->annotationReader->getMethodAnnotation($reflection->getMethod((string) ($methodName ?? '__invoke')), RateLimitAnnotation::class);
6965

70-
if (!$annotation instanceof RateLimitAnnotation && !$annotation instanceof GraphQLRateLimitAnnotation) {
66+
if (!$annotation instanceof RateLimitAnnotation) {
7167
return;
7268
}
7369

74-
if ($annotation instanceof RateLimitAnnotation) {
70+
$rateLimit = new RateLimit(
71+
$this->limit,
72+
$this->period
73+
);
74+
75+
if ($this->limitByRoute) {
7576
$rateLimit = new RateLimit(
76-
$this->limit,
77-
$this->period
77+
$annotation->getLimit() ?? $this->limit,
78+
$annotation->getPeriod() ?? $this->period
7879
);
7980

80-
if ($this->limitByRoute) {
81-
$rateLimit = new RateLimit(
82-
$annotation->getLimit() ?? $this->limit,
83-
$annotation->getPeriod() ?? $this->period
84-
);
85-
86-
$rateLimit->varyHashOn('_route', $request->attributes->get('_route'));
87-
}
88-
}
89-
90-
if ($annotation instanceof GraphQLRateLimitAnnotation) {
91-
if (!class_exists('GraphQL\Language\Parser')) {
92-
throw new \Exception('Run "composer require webonyx/graphql-php" to use @GraphQLRateLimit annotation.');
93-
}
94-
95-
$endpoint = $this->extractQueryName($request->request->get('query'));
96-
97-
foreach ($annotation->getEndpointConfigurations() as $graphQLEndpointConfiguration) {
98-
if ($endpoint === $graphQLEndpointConfiguration->getEndpoint()) {
99-
$rateLimit = new RateLimit(
100-
$graphQLEndpointConfiguration->getLimit() ?? $this->limit,
101-
$graphQLEndpointConfiguration->getPeriod() ?? $this->period
102-
);
103-
$rateLimit->varyHashOn('_graphql_endpoint', $endpoint);
104-
break;
105-
}
106-
}
107-
if (!isset($rateLimit)) {
108-
return;
109-
}
81+
$rateLimit->varyHashOn('_route', $request->attributes->get('_route'));
11082
}
11183

11284
foreach ($this->rateLimitModifiers as $hashKeyVarier) {
@@ -126,19 +98,4 @@ public static function getSubscribedEvents(): array
12698
ControllerEvent::class => 'onKernelController',
12799
];
128100
}
129-
130-
/**
131-
* @param Source|string $query
132-
*/
133-
public function extractQueryName($query): string
134-
{
135-
$parsedQuery = Parser::parse($query);
136-
/** @var OperationDefinitionNode $item */
137-
foreach ($parsedQuery->definitions->getIterator() as $item) {
138-
/* @phpstan-ignore-next-line */
139-
return (string) $item->selectionSet->selections[0]->name->value;
140-
}
141-
142-
throw new QueryExtractionException('Unable to extract query');
143-
}
144101
}

src/Resources/config/services.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ services:
1313
$limitByRoute: '%bedrock_rate_limit.limit_by_route%'
1414
$rateLimitModifiers: !tagged rate_limit.modifiers
1515

16+
Bedrock\Bundle\RateLimitBundle\EventListener\ReadGraphQLRateLmitAnnotationListener:
17+
arguments:
18+
$limit: '%bedrock_rate_limit.limit%'
19+
$period: '%bedrock_rate_limit.period%'
20+
$rateLimitModifiers: !tagged rate_limit.modifiers
21+
1622
Bedrock\Bundle\RateLimitBundle\EventListener\LimitRateListener:
1723
arguments:
1824
$displayHeaders: '%bedrock_rate_limit.display_headers%'

0 commit comments

Comments
 (0)