Symfony RateLimiter

A PHP rate-limiting component implementing token bucket, sliding window, and fixed window strategies for throttling input and output.

Library
Composer
vv8.1.4
269stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
77/100Good
Development Activity80
Maintenance88
Community60
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
78/100Good
Architecture82
Code Quality81
Innovation70
Learning Curve80

The Symfony RateLimiter component provides a small set of interchangeable rate-limiting policies — token bucket, fixed window, sliding window, and calendar-aligned window — behind a single RateLimiterFactory/LimiterInterface API. It’s usable standalone in any PHP 8.4+ project and is the same component that powers Symfony’s built-in login-throttling and HttpClient rate-limiting features.

Each limiter can either reject requests once a limit is hit (consume()->isAccepted()) or block until capacity frees up (reserve()->wait()), and state can be stored in memory (for single-process use) or in any PSR-6 cache (for rate limiting shared across workers or servers). A CompoundLimiter lets multiple policies be combined so, for example, a short burst allowance and a longer sustained-rate cap can both apply to the same resource.

What You Get

  • Four rate-limiting policies out of the box: TokenBucketLimiter, FixedWindowLimiter, SlidingWindowLimiter, and a CalendarAlignedWindow variant for calendar-period limits (per day/week/month).
  • A RateLimiterFactory that builds a configured limiter from a plain config array (id, policy, limit, rate), plus a CompoundRateLimiterFactory for combining multiple policies into one check.
  • Both non-blocking (consume($tokens)->isAccepted()) and blocking (reserve($tokens)->wait()) acquisition modes for every policy.
  • Pluggable storage: InMemoryStorage for single-process use or CacheStorage (any PSR-6 cache) for state shared across requests, workers, or servers.
  • A NoLimiter no-op implementation for disabling rate limiting in specific environments (e.g. tests) without changing calling code.

Common Use Cases

  • Throttling login attempts to slow down brute-force or credential-stuffing attacks.
  • Rate-limiting outbound HTTP calls to a third-party API that enforces its own request quotas.
  • Limiting API endpoint usage per user or API key in a public-facing PHP API.
  • Applying a burst-plus-sustained rate policy (via CompoundLimiter) to actions like password-reset emails or SMS sends.

Under The Hood

Architecture: RateLimiterFactory reads a config array and instantiates the matching Policy\*Limiter class (token bucket, fixed window, sliding window, or calendar-aligned window), wiring it to a StorageInterface implementation that persists the limiter’s state (LimiterStateInterface) between calls. CompoundLimiter/CompoundRateLimiterFactory wrap several limiters and require all of them to accept a request, which is how burst-plus-sustained-rate policies are composed without new limiter classes. Reservation.php models the result of a reserve() call, carrying the wait time needed before the requested tokens become available.

Tech Stack: PHP 8.4+, depends on symfony/options-resolver for validating the factory’s configuration array, and optionally on any PSR-6 cache implementation (psr/cache) for CacheStorage; symfony/lock is a dev/optional dependency used to make CacheStorage operations atomic under concurrent access.

Code Quality: Tests/ mirrors the Policy/ and Storage/ structure with per-limiter PHPUnit test classes (CompoundLimiterTest.php, RateLimiterFactoryTest.php, RateLimitTest.php, plus per-policy and per-storage tests), run via phpunit.xml.dist. As part of the symfony/symfony monorepo it inherits Symfony’s shared CI and backward-compatibility promise across minor releases.

API Design: The two-method mental model — consume() for immediate accept/reject decisions and reserve()->wait() for blocking until capacity is available — stays identical across all four policies and both storage backends, so switching from a fixed window to a token bucket, or from in-memory to shared cache storage, is a configuration change rather than a code change.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search