Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Rate Limiter Laravel Package

symfony/rate-limiter

Symfony Rate Limiter component implementing token bucket rate limiting. Configure limiters via a factory and use reserve() to wait for tokens or consume() to attempt immediately. Supports pluggable storage like in-memory for controlling request/input/output rates.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require symfony/rate-limiter
    
  2. Basic Usage:

    use Symfony\Component\RateLimiter\RateLimiterFactory;
    use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
    
    // Initialize factory with a unique ID and rate policy
    $factory = new RateLimiterFactory([
        'id' => 'login_attempts',
        'policy' => 'token_bucket',
        'limit' => 5, // Max tokens
        'rate' => ['interval' => '5 minutes'], // Refill rate
    ], new InMemoryStorage());
    
    $limiter = $factory->create();
    
  3. First Use Case:

    • Block until tokens are available (e.g., for critical operations like logins):
      $limiter->reserve(1)->wait();
      // Execute protected logic here
      
    • Non-blocking check (e.g., for optional features):
      if ($limiter->consume(1)->isAccepted()) {
          // Execute logic if tokens are available
      }
      

Where to Look First

  • Documentation: Symfony RateLimiter Docs
  • Source Code: Focus on RateLimiterFactory, TokenBucketRateLimiter, and Storage interfaces.
  • Laravel Integration: Explore how to wrap this in a service provider or facade for consistency.

Implementation Patterns

Core Workflows

1. API Rate Limiting

  • Middleware Integration:
    use Symfony\Component\RateLimiter\RateLimiterInterface;
    
    class RateLimitMiddleware
    {
        public function __construct(private RateLimiterInterface $limiter) {}
    
        public function handle($request, Closure $next)
        {
            if (!$this->limiter->consume(1)->isAccepted()) {
                return response()->json(['error' => 'Rate limit exceeded'], 429)
                    ->header('Retry-After', $this->limiter->getRetryAfter()->format('U'));
            }
            return $next($request);
        }
    }
    
  • Dynamic Limits via Config:
    $factory = new RateLimiterFactory([
        'id' => 'api_endpoint',
        'policy' => 'token_bucket',
        'limit' => config('rate_limits.api.max_attempts'),
        'rate' => ['interval' => config('rate_limits.api.window')],
    ], $storage);
    

2. Queue Job Throttling

  • Laravel Queue Jobs:
    use Symfony\Component\RateLimiter\RateLimiterInterface;
    
    class ProcessPaymentJob implements ShouldQueue
    {
        public function __construct(private RateLimiterInterface $limiter) {}
    
        public function handle()
        {
            $this->limiter->reserve(1)->wait();
            // Process payment
        }
    }
    

3. Compound Rate Limiting

  • Combine Multiple Policies (e.g., per-IP + per-user):
    use Symfony\Component\RateLimiter\CompoundRateLimiterFactory;
    
    $ipLimiter = $factory->create('ip_limit');
    $userLimiter = $factory->create('user_limit');
    
    $compoundFactory = new CompoundRateLimiterFactory([$ipLimiter, $userLimiter]);
    $compoundLimiter = $compoundFactory->create();
    

4. Storage Backends

  • Redis Integration (for distributed systems):
    use Symfony\Component\RateLimiter\Storage\RedisStorage;
    use Predis\Client;
    
    $redis = new Client(['scheme' => 'tcp', 'host' => 'redis']);
    $storage = new RedisStorage($redis);
    

Integration Tips

Laravel-Specific Patterns

  • Service Provider:

    public function register()
    {
        $this->app->singleton(RateLimiterInterface::class, function ($app) {
            $storage = new RedisStorage($app['redis']);
            return (new RateLimiterFactory([
                'id' => 'default',
                'policy' => 'token_bucket',
                'limit' => 100,
                'rate' => ['interval' => '1 hour'],
            ], $storage))->create();
        });
    }
    
  • Facade:

    // app/Facades/RateLimiter.php
    public static function limit(string $id, int $tokens = 1): bool
    {
        return app(RateLimiterInterface::class)->consume($tokens)->isAccepted();
    }
    

Testing

  • Mock Storage for Unit Tests:

    $storage = $this->createMock(StorageInterface::class);
    $storage->method('get')->willReturn(['tokens' => 10, 'lastConsumed' => now()]);
    $factory = new RateLimiterFactory([...], $storage);
    
  • Behavioral Tests:

    $limiter = $factory->create();
    $this->assertTrue($limiter->consume(1)->isAccepted());
    $this->assertFalse($limiter->consume(10)->isAccepted());
    

Gotchas and Tips

Pitfalls

  1. InMemoryStorage Limitations:

    • Single-Process Only: Tokens are not shared across Laravel workers/queues. Use RedisStorage for distributed systems.
    • State Loss: In-memory storage resets on process restart. Avoid for critical production use.
  2. Token Bucket Quirks:

    • Burst Handling: The token_bucket policy allows short bursts (up to limit tokens). For strict fixed windows, use fixed_window or sliding_window policies.
    • Negative Tokens: Consuming more tokens than available may cause unexpected behavior. Always check isAccepted() or use reserve()->wait().
  3. Retry-After Headers:

    • Precision Issues: retryAfter may not be perfectly accurate due to clock skew. Test with real-world traffic.
    • HTTP Compliance: Ensure headers comply with RFC 6585 for client compatibility.
  4. Storage Backend Bottlenecks:

    • Redis Latency: High-frequency rate limiting (e.g., >10K requests/sec) may overwhelm Redis. Consider sharding or dedicated rate-limiting services.
    • Database Storage: Avoid using Laravel’s database as storage—it’s not optimized for high-throughput rate limiting.

Debugging Tips

  1. Log Rate Limit Events:

    $limiter->consume(1)->isAccepted(); // Returns a Result object
    if (!$result->isAccepted()) {
        \Log::warning('Rate limit exceeded', [
            'retry_after' => $result->getRetryAfter()->diffInSeconds(),
            'remaining' => $result->getRemainingTokens(),
        ]);
    }
    
  2. Inspect Storage State:

    $storage = $factory->getStorage();
    $state = $storage->get('login_attempts'); // Debug raw storage
    
  3. Common Issues:

    • "Token underflow" errors: Upgrade to Symfony 7.4.7+ or patch your code to handle negative tokens.
    • Clock skew: Ensure all Laravel instances use synchronized time (e.g., NTP). Test with DateTime::getLastErrors().

Extension Points

  1. Custom Storage:

    class DatabaseStorage implements StorageInterface
    {
        public function get(string $id): array
        {
            return DB::table('rate_limits')->where('id', $id)->first();
        }
    
        public function set(string $id, array $state): void
        {
            DB::table('rate_limits')->updateOrInsert(
                ['id' => $id],
                ['state' => json_encode($state)]
            );
        }
    }
    
  2. Dynamic Policies:

    $factory = new RateLimiterFactory([
        'id' => 'dynamic_limit',
        'policy' => function (StorageInterface $storage) {
            $config = config('rate_limits.dynamic');
            return new TokenBucketRateLimiter(
                $storage,
                $config['limit'],
                new \DateInterval($config['interval'])
            );
        },
    ], $storage);
    
  3. Event-Driven Rate Limiting:

    • Trigger events when limits are hit:
      $limiter->consume(1)->onExceeded(function (Result $result) {
          event(new RateLimitExceeded($result->getRetryAfter()));
      });
      

Laravel-Specific Quirks

  1. Queue Workers:

    • Shared Storage: Ensure all queue workers (e.g., Laravel Horizon, Supervisor) use the same storage backend (Redis recommended).
    • Process Isolation: In-memory storage will reset for each worker process. Use Redis for shared state.
  2. Artisan Commands:

    • Rate-Limited CLI:
      $limiter
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport