@@ -547,6 +547,123 @@ at all):
547547 lock factory (``lock.factory ``) failed if the Symfony Lock component was not
548548 installed in the application.
549549
550+ Compound Rate Limiter
551+ ---------------------
552+
553+ .. versionadded :: 7.3
554+
555+ Configuring compound rate limiters was added in 7.3.
556+
557+ You can configure multiple rate limiters to work together:
558+
559+ .. configuration-block ::
560+
561+ .. code-block :: yaml
562+
563+ # config/packages/rate_limiter.yaml
564+ framework :
565+ rate_limiter :
566+ two_per_minute :
567+ policy : ' fixed_window'
568+ limit : 2
569+ interval : ' 1 minute'
570+ five_per_hour :
571+ policy : ' fixed_window'
572+ limit : 5
573+ interval : ' 1 hour'
574+ contact_form :
575+ policy : ' compound'
576+ limiters : [two_per_minute, five_per_hour]
577+
578+ .. code-block :: xml
579+
580+ <!-- config/packages/rate_limiter.xml -->
581+ <?xml version =" 1.0" encoding =" UTF-8" ?>
582+ <container xmlns =" http://symfony.com/schema/dic/services"
583+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
584+ xmlns : framework =" http://symfony.com/schema/dic/symfony"
585+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
586+ https://symfony.com/schema/dic/services/services-1.0.xsd
587+ http://symfony.com/schema/dic/symfony
588+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" >
589+
590+ <framework : config >
591+ <framework : rate-limiter >
592+ <framework : limiter name =" two_per_minute"
593+ policy =" fixed_window"
594+ limit =" 2"
595+ interval =" 1 minute"
596+ />
597+
598+ <framework : limiter name =" five_per_hour"
599+ policy =" fixed_window"
600+ limit =" 5"
601+ interval =" 1 hour"
602+ />
603+
604+ <framework : limiter name =" contact_form"
605+ policy =" compound"
606+ >
607+ <limiter >two_per_minute</limiter >
608+ <limiter >five_per_hour</limiter >
609+ </framework : limiter >
610+ </framework : rate-limiter >
611+ </framework : config >
612+ </container >
613+
614+ .. code-block :: php
615+
616+ // config/packages/rate_limiter.php
617+ use Symfony\Config\FrameworkConfig;
618+
619+ return static function (FrameworkConfig $framework): void {
620+ $framework->rateLimiter()
621+ ->limiter('two_per_minute')
622+ ->policy('fixed_window')
623+ ->limit(2)
624+ ->interval('1 minute')
625+ ;
626+
627+ $framework->rateLimiter()
628+ ->limiter('two_per_minute')
629+ ->policy('fixed_window')
630+ ->limit(5)
631+ ->interval('1 hour')
632+ ;
633+
634+ $framework->rateLimiter()
635+ ->limiter('contact_form')
636+ ->policy('compound')
637+ ->limiters(['two_per_minute', 'five_per_hour'])
638+ ;
639+ };
640+
641+ Then, inject and use as normal::
642+
643+ // src/Controller/ContactController.php
644+ namespace App\Controller;
645+
646+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
647+ use Symfony\Component\HttpFoundation\Request;
648+ use Symfony\Component\HttpFoundation\Response;
649+ use Symfony\Component\RateLimiter\RateLimiterFactory;
650+
651+ class ContactController extends AbstractController
652+ {
653+ public function registerUser(Request $request, RateLimiterFactoryInterface $contactFormLimiter): Response
654+ {
655+ $limiter = $contactFormLimiter->create($request->getClientIp());
656+
657+ if (false === $limiter->consume(1)->isAccepted()) {
658+ // either of the two limiters has been reached
659+ }
660+
661+ // ...
662+ }
663+
664+ // ...
665+ }
666+
550667.. _`DoS attacks` : https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
551668.. _`Apache mod_ratelimit` : https://httpd.apache.org/docs/current/mod/mod_ratelimit.html
552669.. _`NGINX rate limiting` : https://www.nginx.com/blog/rate-limiting-nginx/
0 commit comments