PHP Luminova: Request Throttling
Throttler class offers a simple and efficient way to control request frequency, helping you prevent abuse and regulate access across your application.
The Throttler
class is a lightweight tool designed for request throttling. It helps enforce usage limits by tracking how often a client—identified by a unique key such as a user ID, token, or an IP address makes requests within a defined time frame. When the limit is reached, the throttler can delay or block further requests.
This class supports various backend cache, including FileCache
, Redis
, Memcached
, and any PSR-6 or PSR-16 compatible cache implementations. You define a maximum request count (limit
) and the time window in seconds (ttl
). Once the threshold is met, optional delay mechanisms can be applied before continuing execution.
The Throttler
is ideal for quick integration where flexible request regulation is needed, and it fits naturally into application-level hooks, controllers, or route handlers.
Key Features:
- Supports PSR cache interfaces and Luminova cache classes.
- Lightweight and fast—optimized for runtime throttling.
- Easy to integrate at any point in the application lifecycle.
- Configurable limits and durations with delay control.
- Stateless and cache-driven for consistent behavior across requests.
Supported Cache Backends
You can use different caching systems to store throttle data depending on your setup. The throttler is compatible with various backends.
File Cache (default fallback):
use Luminova\Cache\FileCache;
$path = root('/writeable/caches/throttler');
$cache = new FileCache($path);
Redis:
use Redis;
$cache = new Redis();
$cache->connect('127.0.0.1', 6379);
Memcached:
use Memcached;
$cache = new Memcached();
$cache->addServer('127.0.0.1', 11211);
Usage Examples
Basic Request Throttling
Apply throttling logic to limit how often a user or client can access a resource within a defined time window.
use Luminova\Security\Throttler;
use Memcached;
$key = 'user-id';
$cache = new Memcached();
$cache->addServer('127.0.0.1', 11211);
$throttle = new Throttler(cache: $cache, limit: 10, ttl: 60);
if ($throttle->throttle($key)->isExceeded()) {
$throttle->delay(5);
}
echo 'API Response';
Synchronous Throttling
Performs a blocking wait during the throttling check. This ensures the request only proceeds once the check is complete.
$throttle = new Throttler(cache: $cache, limit: 10, ttl: 60);
$throttle->throttle($key)->wait();
if ($throttle->isExceeded()) {
$throttle->delay(5);
}
echo 'API Response';
Advanced Usage
Application-Wide Request Throttling
To apply global throttling across your entire application, you can implement throttling logic inside the onPreCreate()
method of your application class. This ensures the rate limit is enforced before routing or services are initialized, making it ideal for protecting public APIs or high-traffic endpoints.
By using this early hook, requests that exceed the limit can be slowdown immediately, saving system resources and reducing potential abuse.
// /app/Application.php
namespace App;
use Luminova\Core\CoreApplication;
use Luminova\Security\Throttler;
use Memcached;
class Application extends CoreApplication
{
protected function onPreCreate(): void
{
// Optionally combine API user ID and client IP for unique identification
$key = request()->header->get('X-Api-User-Id') . ip_address();
// Initialize your cache adapter (PSR-16 supported, e.g., Memcached)
$cache = new Memcached();
$cache->addServer('127.0.0.1', 11211);
// Allow max 10 requests per 60 seconds
$throttle = new Throttler(
cache: $cache,
limit: 10,
ttl: 60
);
// Check if the limit has been exceeded
if ($throttle->throttle($key)->isExceeded()) {
// Optional: Introduce a delay or throw an exception
$throttle->delay(5); // Sleep for 5 seconds
}
}
}
Why Use This?
- Applies to every request — no need to wrap every controller or route.
- Stops abuse early — before any app logic is executed.
Controller Scope Throttling
Use request throttling at the controller level to apply custom limits per endpoint or to exclude certain routes from global rules.
In onCreate()
Method (Controller-wide)
// /app/Controllers/Http/ApiController.php
namespace App\Controllers\Http;
use Luminova\Base\BaseController;
use Luminova\Security\Throttler;
use Memcached;
class ApiController extends BaseController
{
protected function onCreate(): void
{
$key = request()->header->get('X-Api-User-Id') . ip_address();
$cache = new Memcached();
$cache->addServer('127.0.0.1', 11211);
$throttle = new Throttler(cache: $cache, limit: 10, ttl: 60);
if ($throttle->throttle($key)->isExceeded()) {
$throttle->delay(5);
}
}
}
As Middleware (Before Route Execution)
Apply request throttling in the controller's before middleware to control access before route logic runs. This ensures limits are applied early, before rendering views or executing business logic.
// /app/Controllers/Http/ApiController.php
namespace App\Controllers\Http;
use Luminova\Base\BaseController;
use Luminova\Security\Throttler;
use Luminova\Attributes\Route;
use Luminova\Attributes\Prefix;
use Luminova\Http\Request;
use Memcached;
#[Prefix(pattern: '/api(:root)')]
class ApiController extends BaseController
{
#[Route('/api/(:root)', methods: ['ANY'], middleware: Route::BEFORE_MIDDLEWARE)]
public function middleware(Request $request): int
{
$key = $request->header->get('X-Api-User-Id');
$key .= ip_address();
$cache = new Memcached();
$cache->addServer('127.0.0.1', 11211);
$throttle = new Throttler(cache: $cache, limit: 10, ttl: 60);
if ($throttle->throttle($key)->isExceeded()) {
$throttle->delay(5);
}
return STATUS_SUCCESS;
}
}
Per-Route Request Throttling
Apply request throttling directly inside a controller method to control access on a specific endpoint. This approach allows fine-grained control just before executing the main view logic.
// /app/Controllers/Http/ApiController.php
namespace App\Controllers\Http;
use Luminova\Base\BaseController;
use Luminova\Security\Throttler;
use Luminova\Attributes\Route;
use Luminova\Http\Request;
use App\Models\Users;
use Memcached;
class ApiController extends BaseController
{
#[Route('/api/users/', methods: ['GET'])]
public function users(Request $request): int
{
$key = $request->header->get('X-Api-User-Id');
$key .= ip_address();
$cache = new Memcached();
$cache->addServer('127.0.0.1', 11211);
$throttle = new Throttler(cache: $cache, limit: 10, ttl: 60);
if ($throttle->throttle($key)->isExceeded()) {
$throttle->delay(5);
}
return response()->json([
'data' => (new Users())->select()
]);
}
}
- Class namespace:
\Luminova\Security\Throttler
- This class implements:\Luminova\Interface\LazyInterface
Methods
constructor
Create a new Throttler instance for rate limiting.
This constructor sets up the rate-limiting logic using various supported caching backends.It tracks client requests (typically via IP) and enforces request limits within a time window.
Supported Cache Types:
- PSR-6:
CacheItemPoolInterface
- PSR-16:
CacheInterface
- Luminova's
BaseCache
(file or memory cache) - Native
Memcached
orRedis
instance PredisClient
(Predis library)
public __construct(
Psr\Cache\CacheItemPoolInterface|Psr\SimpleCache\CacheInterface|Luminova\Base\BaseCache|Memcached|Predis\Client|Redis|null $cache = null,
int $limit = 5,
DateInterval|int $ttl = 10,
string $persistentId = ''
): mixed
Parameters:
Parameter | Type | Description |
---|---|---|
$cache | mixed | Optional cache backend. Defaults to Luminova's file-based cache if not provided. |
$limit | int | Maximum number of allowed requests in the time window (default: 5). |
$ttl | DateInterval|int | Time window duration in seconds or as a DateInterval (default: 10). |
$persistentId | string | Optional unique identifier to include in the rate-limiting key. |
setCache
Set the cache instance used for rate limiting.
This method allows you to provide a custom caching backend to storerate-limiting data. Supported cache types include:
- PSR-6 (
CacheItemPoolInterface
) and PSR-16 (CacheInterface
) implementations - Native
Memcached
orRedis
instances PredisClient
instance- Luminova's custom
BaseCache
If no cache is set explicitly, the default fallback is Luminova's file-based cache.
public setCache(
Psr\Cache\CacheItemPoolInterface|Psr\SimpleCache\CacheInterface|Luminova\Base\BaseCache|Memcached|Predis\Client|Redis $cache
): self
Parameters:
Parameter | Type | Description |
---|---|---|
$cache | mixed | The cache instance used to store limiter data. |
Return Value:
self
- Returns the instance of Throttler class.
getLimit
Get the maximum number of allowed requests within the time window.
public getLimit(): int
Return Value:
int
- Return the configured request limit.
getIp
Retrieves the stored client IP address associated with the current request context.
public getIp(): ?string
Return Value:
string|null
- Return the IP address if available, or null if not set.
getTimestamps
Get the current number of request attempts made during the current time window.
public getTimestamps(): array
Return Value:
array
- Return the number of requests made so far.
getRestAfter
Get the number of seconds until throttle resets.
public getRestAfter(): ?int
Return Value:
int|null
- Return the seconds until the throttle resets, or null
if not throttled.
getTimeWindow
Get the time window in seconds used for rate limiting.
public getTimeWindow(): int
Return Value:
int
- Return the duration of the rate-limiting window in seconds.
isExceeded
Retrieves the result after performing the is
method.
public isExceeded(): bool
Return Value:
bool
- Returns true if the request is allowed, false if the rate limit has been reached.
isIpAddress
Determine if the stored IP address matches the current request IP.
Useful for validating that the limiter data corresponds to the current client, especially when using persistent keys shared across multiple sessions or users.
public isIpAddress(): bool
Return Value:
bool
- Return true if IP matches or if no IP is stored; false otherwise.
throttle
Applies throttling based on a unique key or client IP address.
This method initializes the rate-throttling check using a hashed key derived from the client's IP address and/or a provided custom identifier. It tracks the request timestamps to determine whether the limit has been exceeded within the configured time window (ttl
) and limit frequency of requests to prevent from overload, ensure fair usage, and optimize performance
public throttle(?string $key = null): self
Parameters:
Parameter | Type | Description |
---|---|---|
$key | string|null | Optional custom identifier (e.g., User-Id ) for distinguishing between request entities.If not provided, the client's IP address is used. |
Return Value:
self
- Returns the instance of Throttler class, reflecting the status.
Throws:
- \Luminova\Exceptions\InvalidArgumentException - If the cache instance is invalid or the key is empty.
- \Luminova\Exceptions\RuntimeException - If the cache connection is unavailable.
wait
Waits until the rate throttling finishes processing or the optional timeout is reached.
This is a passive wait that periodically checks if the throttling decision is complete.It is useful when throttling involves asynchronous or deferred logic.
public wait(float|int $interval = 1.0, ?int $maxWait = null): self
Parameters:
Parameter | Type | Description |
---|---|---|
$interval | float|int | The interval to sleep between checks (in seconds). Supports sub-second delays (e.g., 0.1 for 100ms). |
$maxWait | int|null | The maximum duration to wait (in seconds). Use null to wait indefinitely. |
Return Value:
self
- Returns the instance of Throttler class.
delay
Introduces an execution delay if the rate limit was exceeded.
This can be used to enforce backoff or slow down abusive clients after a throttle breach.If the limit was not exceeded, this method exits immediately.
public delay(float|int $interval = 1.0): void
Parameters:
Parameter | Type | Description |
---|---|---|
$interval | float|int | Delay duration in seconds. Supports sub-second values (e.g., 0.5 for 500ms). |
reset
Reset remaining rate to initial limit and consumed request to 0.
Clears all stored timestamps and restores the available request count to the initial limit for the active client key.
public reset(?string $key = null): bool
Parameters:
Parameter | Type | Description |
---|---|---|
$key | string|null | An optional unique identifier to reset requests (e.g, User-Id ).Default to client IP address if key is not provided. |
Return Value:
bool
- Returns true request key was cleared, otherwise false.
Throws:
- \Luminova\Exceptions\InvalidArgumentException - If the cache instance is not valid or the key is empty.
- \Luminova\Exceptions\RuntimeException - If cache is not connected.