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