RateLimitMiddleware(global) — checks the hit counter in a cache and, when a rate limit is attached to the current route, either lets the request through or returns429 Too Many Requests.RateLimitInterface(per route) — the policy object on theRoute. Ships withIpRateLimit; bring your own for anything else.
1. Register the middleware once
The middleware needs a cache to store counters. AnyCacheInterface works — it can be the same one you use for the router index.
2. Attach a limit to a specific route
IpRateLimit throttles by client IP. The constructor is (string $name, int $maxAttempts, int $windowInSeconds).
rateLimit(...) bypass the limiter entirely — RateLimitMiddleware no-ops them. Only routes with a policy attached ever land in the cache.
3. What happens at request time
On every hit,RateLimiter::allow() increments a counter keyed by getKey(RateLimitContext). If the incremented value exceeds getMaxAttempts() within the window, RateLimitMiddleware short-circuits with TooManyRequestsApivalkResponse (HTTP 429). Either way, the RateLimitResult is set on the request and the MiddlewareStack appends these headers to every response for the route:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Max requests in the window |
X-RateLimit-Remaining | Requests remaining |
X-RateLimit-Reset | UTC epoch when the window resets |
Retry-After | Seconds (or epoch) the client should wait — only on 429 |
4. Custom strategies
ImplementRateLimitInterface to key on anything other than IP — API key, user id, tenant.
RateLimitContext is built per-request; it exposes the route and the request, so any identifier you can get from those is fair game for the cache key. Order matters: the route’s rateLimit(...) must be called after whatever middleware populates that identifier — AuthenticationMiddleware for auth-keyed limits.
5. Stacking limits
Only onerateLimit() per route. If you need “5 req/s + 1000 req/day”, implement a composite RateLimitInterface that owns two counters internally and returns the stricter one.
6. Testing locally
BecauseFilesystemCache persists counters to disk, restart behaviour is: counters survive PHP restarts but disappear when you clear the cache directory. Clearing it is the fastest way to reset a limit during development.
Reference
Routing / Rate Limit walks through theRateLimitResult shape and header serialisation. Middleware / Rate Limit covers middleware-level concerns.