thamtech/yii2-ratelimiter-advanced
Advanced rate limiter filter for Yii2 using a leaky-bucket algorithm. Define multiple independent limits per action and identifier (IP, user ID, etc.), store allowance/timestamp automatically, customize responses (429, events, headers, callbacks), and support Retry-After.
Installation
composer require thamtech/yii2-ratelimiter-advanced
Add to your Yii2 config/web.php:
'components' => [
'rateLimiter' => [
'class' => 'thamtech\ratelimiter\RateLimiter',
'storage' => 'cache', // or 'db' for database-backed storage
],
],
First Use Case: Basic Rate Limiting Apply to a controller action:
use thamtech\ratelimiter\RateLimiter;
public function actionExample(RateLimiter $rateLimiter) {
$key = 'user:123:action:example';
if (!$rateLimiter->allow($key, 10, 60)) { // 10 requests per 60 seconds
throw new \yii\web\HttpException(429, 'Too many requests');
}
// Action logic...
}
Key Resources
cache/db).RateLimiter class docs for methods like allow(), reset(), and getRemaining().User-Based Rate Limiting
$key = 'user:' . Yii::$app->user->id . ':api:endpoint';
if (!$rateLimiter->allow($key, 5, 30)) { // 5 requests per 30 seconds
return $this->asJson(['error' => 'Rate limit exceeded'], 429);
}
IP-Based Rate Limiting (Fallback)
$key = 'ip:' . Yii::$app->request->userIP . ':login';
if (!$rateLimiter->allow($key, 3, 60)) {
throw new \yii\web\HttpException(429, 'Too many login attempts');
}
Dynamic Rate Limits via Config
Define limits in config/params.php:
'rateLimits' => [
'api:create' => ['max' => 10, 'window' => 60],
'api:read' => ['max' => 100, 'window' => 60],
],
Use in controller:
$limit = Yii::$app->params['rateLimits']['api:create'];
$key = 'user:' . Yii::$app->user->id . ':api:create';
if (!$rateLimiter->allow($key, $limit['max'], $limit['window'])) { ... }
Middleware Integration Create a middleware to enforce global limits:
namespace app\middleware;
use thamtech\ratelimiter\RateLimiter;
class RateLimitMiddleware {
public function __construct(RateLimiter $rateLimiter) {
$this->rateLimiter = $rateLimiter;
}
public function beforeAction($action) {
$key = 'ip:' . Yii::$app->request->userIP . ':' . $action->id;
if (!$this->rateLimiter->allow($key, 100, 60)) {
throw new \yii\web\HttpException(429);
}
}
}
Register in config/web.php:
'components' => [
'request' => [
'enableCsrfValidation' => false, // Optional: Disable if using middleware
],
],
'middleware' => [
'rateLimit' => ['class' => app\middleware\RateLimitMiddleware::class],
],
Database Storage for Persistence
Configure in web.php:
'rateLimiter' => [
'class' => 'thamtech\ratelimiter\RateLimiter',
'storage' => [
'class' => 'thamtech\ratelimiter\storage\DbStorage',
'tableName' => '{{%rate_limits}}',
],
],
Run migrations (check migrations/ in the package for schema).
Cache vs. Database Storage
Key Collisions
'user:123'). Specify actions/resources:
// Bad: 'user:123' (limits all actions for user)
// Good: 'user:123:api:create' (scopes to one endpoint)
Concurrency Issues
$rateLimiter->allow($key, 100, 60, 'fixed'); // Fixed window (simpler but less precise)
Memory Leaks
$rateLimiter->allow($key, 10, 60, null, 3600); // 1-hour TTL for the key
Yii2 Dependency
Cache and Db components. Custom storage requires extending thamtech\ratelimiter\storage\StorageInterface.Check Remaining Tokens
$remaining = $rateLimiter->getRemaining($key);
Yii::debug("Remaining tokens: $remaining");
Reset Limits Manually
$rateLimiter->reset($key); // Reset a specific key
$rateLimiter->resetAll(); // Clear all limits (use cautiously!)
Log Rate Limit Events
if (!$rateLimiter->allow($key, 10, 60)) {
Yii::warning("Rate limit exceeded for $key");
throw new \yii\web\HttpException(429);
}
Monitor Storage
DbStorage, check the {{%rate_limits}} table for stale entries:
SELECT * FROM {{%rate_limits}} WHERE expires_at < NOW();
Custom Storage
Implement thamtech\ratelimiter\storage\StorageInterface:
class RedisStorage implements StorageInterface {
public function get($key) { ... }
public function set($key, $value, $ttl) { ... }
public function delete($key) { ... }
}
Use in config:
'storage' => ['class' => app\storage\RedisStorage::class],
Event-Driven Limits
Extend the RateLimiter class to trigger events:
class ExtendedRateLimiter extends RateLimiter {
public function allow($key, $max, $window, $algorithm = null) {
$allowed = parent::allow($key, $max, $window, $algorithm);
if (!$allowed) {
Yii::$app->trigger('rateLimitExceeded', [$key]);
}
return $allowed;
}
}
Dynamic Rate Adjustment
Override getRate() to fetch limits from an API or DB:
$rateLimiter->setRateResolver(function($key) {
return ['max' => 5, 'window' => 60]; // Or fetch from DB/API
});
Header-Based Enforcement Add headers for API clients:
if (!$rateLimiter->allow($key, 100, 60)) {
Yii::$app->response->getHeaders()->add(
'X-RateLimit-Limit', 100
);
Yii::$app->response->getHeaders()->add(
'X-RateLimit-Remaining', 0
);
throw new \yii\web\HttpException(429);
}
How can I help you explore Laravel packages today?