phil / rate-limit-bundle
Rate limits declared on Symfony controllers, routes and forms: an attribute, a route default and a form option naming limiters over symfony/rate-limiter, keys hashed from the client's address or the user, 429 answers with Retry-After and the IETF RateLimit headers, JSON problems for APIs, a Twig pag
Package info
github.com/parler-haut-interagir-librement/rate-limit-bundle
Type:symfony-bundle
pkg:composer/phil/rate-limit-bundle
Requires
- php: >=8.5
- phil/rate-limit: ^0.1@dev
- psr/clock: ^1.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- symfony/config: ^8.2
- symfony/dependency-injection: ^8.2
- symfony/http-foundation: ^8.2
- symfony/http-kernel: ^8.2
- symfony/rate-limiter: ^8.2
- symfony/routing: ^8.2
Requires (Dev)
- phil/ip-anonymizer: ^0.1@dev
- phpunit/phpunit: ^13.4
- psr/cache: ^3.0
- psr/log: ^3.0
- roave/security-advisories: dev-latest
- symfony/browser-kit: ^8.2
- symfony/cache: ^8.2
- symfony/clock: ^8.2
- symfony/event-dispatcher: ^8.2
- symfony/form: ^8.2
- symfony/framework-bundle: ^8.2
- symfony/options-resolver: ^8.2
- symfony/security-core: ^8.2
- symfony/translation: ^8.2
- symfony/translation-contracts: ^3.6
- symfony/twig-bundle: ^8.2
- twig/twig: ^3.21
Suggests
- phil/ip-anonymizer: To count the ip keys by network, so that an IPv6 subscriber's /64 shares one key
- psr/log: For the warning logged when a limiter in observation would have refused a request
- symfony/form: To limit the submissions of forms by their name
- symfony/lock: For exact counts when requests of one key run at once
- symfony/security-core: For the user and user_or_ip keys of the signed-in user
- symfony/translation: For the refusal page and the form error in the request's language
- twig/twig: For the HTML page of a refusal
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-12 23:52:03 UTC
README
Rate limits declared on Symfony controllers, routes and forms.
phil/rate-limit-bundle holds requests to named limiters: #[RateLimit] on a controller or an action, the route default
_rate_limit, and the rate_limit option of a form. The limiters are fixed windows, sliding windows, token buckets and
compounds of symfony/rate-limiter, declared in the bundle's configuration with their storage, or those of other
packages. Each request counts under a key — the client's address, the signed-in user, the address and the route, or the
application's own —, always a keyed hash, never an address in clear. A refusal is answered 429 Too Many Requests with
Retry-After and the RateLimit-Policy and RateLimit fields of the IETF draft: a problem in JSON to an API, a Twig
page to a browser, an error on the form. Each refusal is dispatched as an event.
Status
| Part | State |
|---|---|
#[RateLimit] on classes and actions, the route default _rate_limit |
Implemented and tested |
| Fixed window, sliding window, token bucket, compound and factory limiters, checked at build | Implemented and tested |
Keys ip, user, user_or_ip, ip_route and the application's, the network with phil/ip-anonymizer |
Implemented and tested |
429 with Retry-After, draft or legacy RateLimit headers, problem JSON, Twig page in English and French |
Implemented and tested |
| Form submissions limited by form name | Implemented and tested |
The RateLimitExceeded event, limiters of other packages by tag |
Implemented and tested |
Limiters in observation: the RateLimitObserved event and a warning, without refusing |
Implemented and tested |
A store local to each server refused in prod, with the guard of phil/rate-limit |
Implemented and tested |
Documentation
| Document | What it covers |
|---|---|
| Installation | Requirements, the bundle, the storage. |
| Configuration | Every option, with an example. |
| Limiting requests | The attribute, the route default, the order of the rules. |
| Keys | Who a request counts for, and how the keys are hashed. |
| Responses | The 429, its headers, the problem in JSON, the page, the event. |
| Forms | Limiting the submissions of a form by its name. |
| Extension points | Limiters, key strategies, the event, the template. |
| Testing | The bundle's tests and its benchmark. |
| Reference | Every class and file of the bundle. |
Quick start
// config/bundles.php Phil\RateLimitBundle\PhilRateLimitBundle::class => ['all' => true],
# config/packages/phil_rate_limit.yaml phil_rate_limit: cache_pool: cache.rate_limits limiters: api: { policy: sliding_window, limit: 100, interval: '1 minute' } login: { policy: fixed_window, limit: 5, interval: '15 minutes', key: ip_route } contact: { policy: token_bucket, limit: 3, rate: { interval: '10 minutes' } } forms: contact: contact
use Phil\RateLimitBundle\Domain\Attribute\RateLimit; #[RateLimit('api')] final class SearchController { #[RateLimit('login')] public function login(): Response }
Requirements
PHP 8.5 or later and Symfony 8.2 or later with FrameworkBundle; TwigBundle for the refusal page, the form component for forms, security for the user keys, phil/ip-anonymizer for network keys, a PSR-3 logger for the warnings of a limiter in observation. phil/rate-limit, required, guards the stores in production.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.