Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
94.74% covered (success)
94.74%
18 / 19
92.31% covered (success)
92.31%
12 / 13
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserRateLimitSubscriber
100.00% covered (success)
100.00%
23 / 23
94.74% covered (success)
94.74%
18 / 19
92.31% covered (success)
92.31%
12 / 13
100.00% covered (success)
100.00%
6 / 6
11.06
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMainRequest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 routeShouldBeLimited
100.00% covered (success)
100.00%
5 / 5
83.33% covered (warning)
83.33%
5 / 6
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
2.15
 limitShouldBeApplied
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 onKernelController
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getSubscribedEvents
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\User\Infrastructure\EventSubscriber;
4
5use Symfony\Bundle\SecurityBundle\Security;
6use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpKernel\Event\ControllerEvent;
9use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
10use Symfony\Component\HttpKernel\HttpKernelInterface;
11use Symfony\Component\HttpKernel\KernelEvents;
12use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
13
14/**
15 * This rate limit applies to the DoctrineUser 'slice' only
16 * Other slices may have their own subscribers (or not)
17 * TODO : maybe we should consider the rate limit a global feature instead or relying on each slice to implement it
18 * Although it may seem 'cleaner' to have each slice implement it, it may result in code duplication
19 * Moreover, should slices become isolated micro services, the rate limiting would probably be handled differently
20 * We should make a decision, but it's not that important at the moment
21 *
22 * @author Wilhelm Zwertvaegher
23 *
24 */
25readonly class UserRateLimitSubscriber implements EventSubscriberInterface
26{
27    public function __construct(
28        private Security                    $security,
29        private RateLimiterFactoryInterface $publicApiByIpLimiter,
30        private RateLimiterFactoryInterface $apiByUserLimiter
31    ) {
32    }
33
34    public function isMainRequest(ControllerEvent $event): bool
35    {
36        return $event->getRequestType() === HttpKernelInterface::MAIN_REQUEST;
37    }
38
39    public function routeShouldBeLimited(Request $request): bool
40    {
41        $routesToLimit = [
42            'api_user_me',
43        ];
44        $route = $request->attributes->get('_route');
45        return $route && in_array($route, $routesToLimit, true);
46    }
47
48    public function limitShouldBeApplied(ControllerEvent $event): bool
49    {
50        return $this->isMainRequest($event) && $this->routeShouldBeLimited($event->getRequest());
51    }
52
53    public function onKernelController(ControllerEvent $event): void
54    {
55        if (!$this->limitShouldBeApplied($event)) {
56            return;
57        }
58
59        $request = $event->getRequest();
60        $user = $this->security->getUser();
61
62        $factory = $this->publicApiByIpLimiter;
63        $key = $request->getClientIp();
64
65        if ($user) {
66            $factory = $this->apiByUserLimiter;
67            $key = $user->getUserIdentifier();
68        }
69        $limiter = $factory->create($key);
70        if (!$limiter->consume()->isAccepted()) {
71            throw new TooManyRequestsHttpException();
72        }
73    }
74
75    public static function getSubscribedEvents(): array
76    {
77        return [
78            KernelEvents::CONTROLLER => 'onKernelController',
79        ];
80    }
81}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

UserRateLimitSubscriber->__construct
27    public function __construct(
28        private Security                    $security,
29        private RateLimiterFactoryInterface $publicApiByIpLimiter,
30        private RateLimiterFactoryInterface $apiByUserLimiter
31    ) {
32    }
UserRateLimitSubscriber->getSubscribedEvents
78            KernelEvents::CONTROLLER => 'onKernelController',
79        ];
80    }
UserRateLimitSubscriber->isMainRequest
34    public function isMainRequest(ControllerEvent $event): bool
35    {
36        return $event->getRequestType() === HttpKernelInterface::MAIN_REQUEST;
37    }
UserRateLimitSubscriber->limitShouldBeApplied
48    public function limitShouldBeApplied(ControllerEvent $event): bool
49    {
50        return $this->isMainRequest($event) && $this->routeShouldBeLimited($event->getRequest());
50        return $this->isMainRequest($event) && $this->routeShouldBeLimited($event->getRequest());
50        return $this->isMainRequest($event) && $this->routeShouldBeLimited($event->getRequest());
51    }
UserRateLimitSubscriber->onKernelController
53    public function onKernelController(ControllerEvent $event): void
54    {
55        if (!$this->limitShouldBeApplied($event)) {
56            return;
59        $request = $event->getRequest();
60        $user = $this->security->getUser();
61
62        $factory = $this->publicApiByIpLimiter;
63        $key = $request->getClientIp();
64
65        if ($user) {
66            $factory = $this->apiByUserLimiter;
67            $key = $user->getUserIdentifier();
68        }
69        $limiter = $factory->create($key);
69        $limiter = $factory->create($key);
70        if (!$limiter->consume()->isAccepted()) {
71            throw new TooManyRequestsHttpException();
73    }
UserRateLimitSubscriber->routeShouldBeLimited
39    public function routeShouldBeLimited(Request $request): bool
40    {
41        $routesToLimit = [
42            'api_user_me',
43        ];
44        $route = $request->attributes->get('_route');
45        return $route && in_array($route, $routesToLimit, true);
45        return $route && in_array($route, $routesToLimit, true);
45        return $route && in_array($route, $routesToLimit, true);
45        return $route && in_array($route, $routesToLimit, true);
45        return $route && in_array($route, $routesToLimit, true);
45        return $route && in_array($route, $routesToLimit, true);
46    }
onKernelController
53    public function onKernelController(ControllerEvent $event): void
54    {
55        if (!$this->limitShouldBeApplied($event)) {
56            return;
59        $request = $event->getRequest();
60        $user = $this->security->getUser();
61
62        $factory = $this->publicApiByIpLimiter;
63        $key = $request->getClientIp();
64
65        if ($user) {
66            $factory = $this->apiByUserLimiter;
67            $key = $user->getUserIdentifier();
68        }
69        $limiter = $factory->create($key);
69        $limiter = $factory->create($key);
70        if (!$limiter->consume()->isAccepted()) {
71            throw new TooManyRequestsHttpException();
73    }
{main}
3namespace App\User\Infrastructure\EventSubscriber;
4
5use Symfony\Bundle\SecurityBundle\Security;
6use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpKernel\Event\ControllerEvent;
9use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
10use Symfony\Component\HttpKernel\HttpKernelInterface;
11use Symfony\Component\HttpKernel\KernelEvents;
12use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
13
14/**
15 * This rate limit applies to the DoctrineUser 'slice' only
16 * Other slices may have their own subscribers (or not)
17 * TODO : maybe we should consider the rate limit a global feature instead or relying on each slice to implement it
18 * Although it may seem 'cleaner' to have each slice implement it, it may result in code duplication
19 * Moreover, should slices become isolated micro services, the rate limiting would probably be handled differently
20 * We should make a decision, but it's not that important at the moment
21 *
22 * @author Wilhelm Zwertvaegher
23 *
24 */
25readonly class UserRateLimitSubscriber implements EventSubscriberInterface
26{
27    public function __construct(
28        private Security                    $security,
29        private RateLimiterFactoryInterface $publicApiByIpLimiter,
30        private RateLimiterFactoryInterface $apiByUserLimiter
31    ) {
32    }
33
34    public function isMainRequest(ControllerEvent $event): bool
35    {
36        return $event->getRequestType() === HttpKernelInterface::MAIN_REQUEST;
37    }
38
39    public function routeShouldBeLimited(Request $request): bool
40    {
41        $routesToLimit = [
42            'api_user_me',
43        ];
44        $route = $request->attributes->get('_route');
45        return $route && in_array($route, $routesToLimit, true);
46    }
47
48    public function limitShouldBeApplied(ControllerEvent $event): bool
49    {
50        return $this->isMainRequest($event) && $this->routeShouldBeLimited($event->getRequest());
51    }
52
53    public function onKernelController(ControllerEvent $event): void
54    {
55        if (!$this->limitShouldBeApplied($event)) {
56            return;
57        }
58
59        $request = $event->getRequest();
60        $user = $this->security->getUser();
61
62        $factory = $this->publicApiByIpLimiter;
63        $key = $request->getClientIp();
64
65        if ($user) {
66            $factory = $this->apiByUserLimiter;
67            $key = $user->getUserIdentifier();
68        }
69        $limiter = $factory->create($key);
70        if (!$limiter->consume()->isAccepted()) {
71            throw new TooManyRequestsHttpException();
72        }
73    }
74
75    public static function getSubscribedEvents(): array
76    {
77        return [
78            KernelEvents::CONTROLLER => 'onKernelController',
79        ];
80    }