Luminova Framework

PHP Luminova: Request Throttling

Last updated: 2025-04-15 16:42:24

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()
        ]);
    }
}


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 or Redis 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:

ParameterTypeDescription
$cachemixedOptional cache backend. Defaults to Luminova's file-based cache if not provided.
$limitintMaximum number of allowed requests in the time window (default: 5).
$ttlDateInterval|intTime window duration in seconds or as a DateInterval (default: 10).
$persistentIdstringOptional 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 or Redis 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:

ParameterTypeDescription
$cachemixedThe 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:

ParameterTypeDescription
$keystring|nullOptional 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:


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:

ParameterTypeDescription
$intervalfloat|intThe interval to sleep between checks (in seconds). Supports sub-second delays (e.g., 0.1 for 100ms).
$maxWaitint|nullThe 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:

ParameterTypeDescription
$intervalfloat|intDelay 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:

ParameterTypeDescription
$keystring|nullAn 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: