@@ -541,6 +541,123 @@ at all):
541
541
lock factory (``lock.factory ``) failed if the Symfony Lock component was not
542
542
installed in the application.
543
543
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
+
544
661
.. _`DoS attacks` : https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
545
662
.. _`Apache mod_ratelimit` : https://httpd.apache.org/docs/current/mod/mod_ratelimit.html
546
663
.. _`NGINX rate limiting` : https://www.nginx.com/blog/rate-limiting-nginx/
0 commit comments