symfony/rate-limiter
Symfony Rate Limiter provides token bucket rate limiting for your app. Create limiters with RateLimiterFactory and a storage backend (e.g., in-memory), then reserve tokens with blocking waits or consume instantly to allow/skip work based on availability.
## Getting Started
### Minimal Setup
1. **Install the package** (unchanged):
```bash
composer require symfony/rate-limiter
Define a rate limiter (unchanged):
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\RedisStorage;
$storage = new RedisStorage('redis://localhost');
$factory = new RateLimiterFactory([
'id' => 'api_endpoint',
'policy' => 'token_bucket',
'limit' => 100,
'rate' => ['interval' => '1 minute'],
], $storage);
$limiter = $factory->create();
First use case: Laravel middleware (unchanged):
use Symfony\Component\HttpFoundation\Response;
class RateLimitMiddleware
{
public function __construct(private RateLimiter $limiter) {}
public function handle($request, Closure $next): Response
{
if (!$this->limiter->consume(1)->isAccepted()) {
return response()->json(['error' => 'Too many requests'], 429)
->header('Retry-After', $this->limiter->getRetryAfter()->format('U'));
}
return $next($request);
}
}
InMemoryStorage (dev), RedisStorage (prod), DatabaseStorage (custom)token_bucket (default), fixed_window, sliding_window(Same as previous assessment)
(Same as previous assessment)
(Same as previous assessment)
(Same as previous assessment)
Use Case: Adjust limits per environment or feature flags.
// config/rate_limits.php
return [
'api' => [
'limit' => env('API_RATE_LIMIT', 100),
'interval' => env('API_RATE_INTERVAL', '1 minute'),
'policy' => env('API_RATE_POLICY', 'token_bucket'), // Explicitly set policy
],
'auth' => [
'limit' => 5,
'interval' => '5 minutes',
],
];
// In middleware:
$config = config('rate_limits.api');
$limiter = (new RateLimiterFactory([
'id' => 'api',
'policy' => $config['policy'],
'limit' => $config['limit'],
'rate' => ['interval' => $config['interval']],
], $storage))->create();
Security Note: Validate serialized data if using custom storage. The new PHPStan rule (Unsafe unserialize) helps detect potential risks in deserialization logic.
Storage Persistence (Unchanged)
RedisStorage in production.Token Bucket Edge Cases (Updated)
reserve()->wait() for blocking operations.Unsafe unserialize) flags unsafe practices. Example:
// Avoid this in custom storage:
$data = unserialize($redis->get('limiter_data')); // Risky!
Time Synchronization (Unchanged)
Retry-After Headers (Unchanged)
DateTimeInterface to Unix timestamp:
->header('Retry-After', $limiter->getRetryAfter()->getTimestamp())
Compound Limiter Short-Circuiting (Unchanged)
Inspect Tokens (Unchanged)
$tokens = $limiter->getTokens();
$retryAfter = $limiter->getRetryAfter();
Log Rate Limit Events (Unchanged)
if (!$limiter->consume(1)->isAccepted()) {
\Log::warning('Rate limit exceeded', [
'retry_after' => $limiter->getRetryAfter()->format('Y-m-d H:i:s'),
'remaining' => $limiter->getTokens(),
]);
}
Test Locally with InMemoryStorage (Unchanged)
$storage = new InMemoryStorage();
$factory = new RateLimiterFactory([...], $storage);
$storage->delete('your_limiter_id');
New: Static Analysis for Security
Unsafe unserialize rule to catch serialization risks:
vendor/bin/phpstan analyse --level=7 src/
Custom Storage (Unchanged)
Implement StorageInterface for database-backed storage.
Security Hardening
class JsonRedisStorage implements StorageInterface
{
public function load($id): array
{
$data = $this->redis->get($id);
return $data ? json_decode($data, true) : [];
}
public function save($id, array $data): void
{
$this->redis->set($id, json_encode($data));
}
}
Policy Validation
$validPolicies = ['token_bucket', 'fixed_window', 'sliding_window'];
if (!in_array($config['policy'], $validPolicies)) {
throw new \InvalidArgumentException('Invalid rate limit policy');
}
NO_UPDATE_NEEDED would not apply here due to the security-focused change in v8.1.1. The assessment has been updated to reflect the new PHPStan rule and added security considerations.
How can I help you explore Laravel packages today?