A framework independent, flexible and highly extensible rate limiter for PHP.
- PHP 8.0 or higher
- desarrolla2/cache ^3.0
composer require sunspikes/php-ratelimiteruseSunspikes\Ratelimit\Cache\Adapter\DesarrollaCacheAdapter;
useSunspikes\Ratelimit\Cache\Factory\DesarrollaCacheFactory;
useSunspikes\Ratelimit\RateLimiter;
useSunspikes\Ratelimit\Throttle\Factory\ThrottlerFactory;
useSunspikes\Ratelimit\Throttle\Hydrator\HydratorFactory;
useSunspikes\Ratelimit\Throttle\Settings\ElasticWindowSettings;
// 1. Make a rate limiter with limit 3 attempts in 10 minutes$cacheAdapter = newDesarrollaCacheAdapter((newDesarrollaCacheFactory())->make());
$settings = newElasticWindowSettings(3, 600);
$ratelimiter = newRateLimiter(newThrottlerFactory($cacheAdapter), newHydratorFactory(), $settings);
// 2. Get a throttler for path /login$loginThrottler = $ratelimiter->get('/login');
// 3. Register a hit$loginThrottler->hit();
// 4. Check if it reached the limitif ($loginThrottler->check()) {
// access permitted
} else {
// access denied
}
// Or combine steps 3 & 4if ($loginThrottler->access()) {
// access permitted
} else {
// access denied
}
// To get the number of hitsprint$loginThrottler->count();By default PHP Ratelimiter uses the desarrolla2 cache adapter, with sample configuration in config/config.php.
You can configure the drivers in config.php, for example to use memcached change the driver to 'memcache':
return [
'default_ttl' => 3600,
'driver' => 'memcache',
'memcache' => [
'servers' => ['localhost'],
],
];Supported drivers: memory, file, apc/apcu, memcache, redis, mysql/mysqli, mongo/mongodb, notcache.
You can use any cache backend by implementing Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface:
useSunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface;
useSunspikes\Ratelimit\Cache\Exception\ItemNotFoundException;
class SymfonyCacheAdapter implements CacheAdapterInterface
{
publicfunction__construct(
privateCacheItemPoolInterface$pool
) {}
publicfunctionget(string$key): mixed { /* ... */ }
publicfunctionset(string$key, mixed$value, ?int$ttl = null): void { /* ... */ }
publicfunctiondelete(string$key): void { /* ... */ }
publicfunctionhas(string$key): bool { /* ... */ }
publicfunctionclear(): void { /* ... */ }
}You can have custom hydrators by implementing Sunspikes\Ratelimit\Throttle\Hydrator\DataHydratorInterface.
For example to use a Symfony Request object for rate limiting:
useSunspikes\Ratelimit\Throttle\Entity\Data;
useSunspikes\Ratelimit\Throttle\Hydrator\DataHydratorInterface;
class RequestHydrator implements DataHydratorInterface
{
publicfunctionhydrate(mixed$data): Data
{
$key = $data->getClientIp() . $data->getPathInfo();
returnnewData($key);
}
}Then decorate or extend the HydratorFactory to recognize your data:
useSunspikes\Ratelimit\Throttle\Hydrator\FactoryInterface;
useSunspikes\Ratelimit\Throttle\Hydrator\DataHydratorInterface;
useSymfony\Component\HttpFoundation\Request;
class MyHydratorFactory implements FactoryInterface
{
publicfunction__construct(
privateFactoryInterface$defaultFactory
) {}
publicfunctionmake(mixed$data): DataHydratorInterface
{
if ($datainstanceof Request) {
returnnewRequestHydrator();
}
return$this->defaultFactory->make($data);
}
}An elastic window throttler will allow X requests in Y seconds. Any further access attempts will be counted, but return false as status. The window is extended with Y seconds on every hit, so there must be no hits during Y seconds for the counter to reset to 0.
See Overview example for instantiation.
The following throttlers use time functions, requiring a different factory for construction:
useSunspikes\Ratelimit\Throttle\Factory\TimeAwareThrottlerFactory;
useSunspikes\Ratelimit\Time\PhpTimeAdapter;
$cacheAdapter = newDesarrollaCacheAdapter((newDesarrollaCacheFactory())->make());
$timeAdapter = newPhpTimeAdapter();
$throttlerFactory = newTimeAwareThrottlerFactory($cacheAdapter, $timeAdapter);
$hydratorFactory = newHydratorFactory();
// $settings = ...$ratelimiter = newRateLimiter($throttlerFactory, $hydratorFactory, $settings);Allows X requests in the Y seconds since the first request. The window does not extend.
useSunspikes\Ratelimit\Throttle\Settings\FixedWindowSettings;
// 120 attempts per minute$settings = newFixedWindowSettings(120, 60);Allows X requests during the previous Y seconds. The window is never extended beyond Y seconds.
useSunspikes\Ratelimit\Throttle\Settings\MovingWindowSettings;
// 120 attempts per minute$settings = newMovingWindowSettings(120, 60);A leaky bucket throttler allows X requests divided over time Y.
Any access attempts past threshold T (default: 0) will be delayed by Y / (X - T).
access() returns false if delayed, hit() returns the number of milliseconds waited.
Note: Time limit for this throttler is in milliseconds, where it is seconds for other throttler types.
useSunspikes\Ratelimit\Throttle\Settings\LeakyBucketSettings;
// 120 attempts per minute, start delaying after 30 requests$settings = newLeakyBucketSettings(120, 60000, 30);Wraps another throttler. When a hit would fail on the internal throttler, the request is delayed until the internal throttler has capacity again.
useSunspikes\Ratelimit\Throttle\Settings\RetrialQueueSettings;
// Leaky bucket that delays any overflow$settings = newRetrialQueueSettings(newLeakyBucketSettings(120, 60000, 120));Krishnaprasad MG [@sunspikes]
Please feel free to send pull requests.
This is open-sourced software licensed under the MIT license.