PHPackages                             snipershady/ratelimiter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. snipershady/ratelimiter

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

snipershady/ratelimiter
=======================

A free and easy-to-use rate limiter

1.0.8(1mo ago)43.6kGPL-3.0-or-laterPHPPHP ^8.3

Since Sep 28Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/snipershady/ratelimiter)[ Packagist](https://packagist.org/packages/snipershady/ratelimiter)[ Docs](https://www.spinfo.it)[ RSS](/packages/snipershady-ratelimiter/feed)WikiDiscussions main Synced 4w ago

READMEChangelog (9)Dependencies (15)Versions (12)Used By (0)

Rate Limiter
============

[](#rate-limiter)

[![PHP Version](https://camo.githubusercontent.com/825a71dd484aeab5c562b9278c9a2e156af831ab68530e68a16bbe25fec46fba/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e332d3838393242462e737667)](https://php.net/)[![License](https://camo.githubusercontent.com/8b59d97f09166dc0016167b7b0cf9a5830ee03c63f582ebd7ed5848ad497f1ef/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d47504c2d2d332e302d626c75652e737667)](https://www.gnu.org/licenses/gpl-3.0.html)[![Packagist](https://camo.githubusercontent.com/55188d5d9a5f8286a1e72b9fbbd82c8ede3933f23855cde6b54ba145563ead00/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736e6970657273686164792f726174656c696d697465722e737667)](https://packagist.org/packages/snipershady/ratelimiter)

A free and easy-to-use rate limiter for PHP applications.

Context
-------

[](#context)

You need to limit network traffic access to a specific function in a specific timeframe. Rate limiting may help to stop some kinds of malicious activity such as brute force attacks, DDoS, and API abuse.

Installation
------------

[](#installation)

```
composer require snipershady/ratelimiter
```

Requirements
------------

[](#requirements)

### Composer packages

[](#composer-packages)

PackageVersionNotesPHP^8.3minimum versionpredis/predis^3.2required only for `CacheEnum::REDIS`### System extensions

[](#system-extensions)

Native PHP extensions are not managed by Composer. Install only the ones needed by the backends you use.

ExtensionRequired byext-apcu`CacheEnum::APCU`ext-redis`CacheEnum::PHP_REDIS`ext-memcached`CacheEnum::MEMCACHED`### Debian / Ubuntu

[](#debian--ubuntu)

```
PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')

# APCu
apt-get install php${PHP_VER}-apcu

# Redis (php-redis native extension)
apt-get install php${PHP_VER}-redis

# Memcached (php-memcached native extension — note the 'd')
apt-get install php${PHP_VER}-memcached
```

### CLI Usage

[](#cli-usage)

For CLI usage, remember to enable APCu in your `php.ini`:

```
apc.enable_cli=1
```

Available Cache Backends
------------------------

[](#available-cache-backends)

BackendEnumDescriptionAPCu`CacheEnum::APCU`Local in-memory cache, no external server requiredPredis`CacheEnum::REDIS`Redis via Predis library (pure PHP)PhpRedis`CacheEnum::PHP_REDIS`Redis via php-redis native extension (better performance)Memcached`CacheEnum::MEMCACHED`Memcached via php-memcached native extensionAPI Reference
-------------

[](#api-reference)

### `isLimited(string $key, int $limit, int $ttl): bool`

[](#islimitedstring-key-int-limit-int-ttl-bool)

Check if a key has exceeded the rate limit.

ParameterTypeDescription`$key`stringUnique identifier for the rate limit (e.g., `__METHOD__`)`$limit`intMaximum number of attempts allowed`$ttl`intTime window in seconds**Returns:** `true` if the limit has been exceeded, `false` otherwise.

### `isLimitedWithBan(string $key, int $limit, int $ttl, int $maxAttempts, int $banTimeFrame, int $banTtl, ?string $clientIp): bool`

[](#islimitedwithbanstring-key-int-limit-int-ttl-int-maxattempts-int-bantimeframe-int-banttl-string-clientip-bool)

Check if a key has exceeded the rate limit, with progressive ban support for repeat offenders. Each violation (a request that exceeds `$limit` within `$ttl`) increments a per-client counter. When that counter reaches `$maxAttempts` within the `$banTimeFrame` observation window, the client is banned: its next time window is extended to `$banTtl` instead of the normal `$ttl`.

ParameterTypeDescription`$key`stringUnique identifier for the rate limit`$limit`intMaximum number of requests allowed in `$ttl` seconds`$ttl`intNormal time window in seconds`$maxAttempts`intNumber of violations allowed before a ban is applied`$banTimeFrame`intObservation window in seconds during which violations are counted. The violation counter resets after `$banTimeFrame` seconds from the first violation, regardless of subsequent activity (fixed window).`$banTtl`intExtended time window in seconds applied when the client is banned (`$banTtl` replaces `$ttl` for the duration of the ban)`$clientIp`string|nullWhen provided, each IP address maintains its own independent violation counter. Pass `null` to apply a shared global counter for the key.**Returns:** `true` if the limit has been exceeded, `false` otherwise.

#### How the three time parameters interact

[](#how-the-three-time-parameters-interact)

```
$ttl          Normal window: max $limit requests every $ttl seconds
$banTimeFrame Observation window: counts how many times the limit was
              exceeded. Resets $banTimeFrame seconds after the first violation.
$banTtl       Punishment window: replaces $ttl when the client has exceeded
              the limit $maxAttempts times within $banTimeFrame seconds.

```

**Concrete timeline** — `$limit=1, $ttl=5s, $maxAttempts=2, $banTimeFrame=30s, $banTtl=120s`:

```
 t=0s   Request 1: allowed  (counter=1, within limit)
 t=1s   Request 2: BLOCKED  → violation #1 recorded, violation TTL=30s starts
 t=6s   Normal window ($ttl=5s) expired
 t=6s   Request 3: allowed  (new window, violation_count=1 < maxAttempts=2)
 t=7s   Request 4: BLOCKED  → violation #2 recorded  ← ban threshold reached!
        violation_count=2 expires at t≈30s (banTimeFrame from t≈1s)
 t=12s  Normal window expired
 t=12s  Request 5: allowed  (new window; but violation_count=2 ≥ maxAttempts
                              → window is extended: this key now lives 120s)
 t=13s  Request 6: BLOCKED  (inside the 120s ban window)
 ...    All requests blocked until t≈132s (t=12 + banTtl=120)
 t=31s  Violation counter expired (banTimeFrame=30s from t≈1s)
 t=132s Ban window ($banTtl=120s) expired
 t=132s Request N: allowed  (violation_count=0, normal $ttl=5s applies again)

```

### `clearRateLimitedKey(string $key): bool`

[](#clearratelimitedkeystring-key-bool)

Remove a rate limit key, resetting its counter.

ParameterTypeDescription`$key`stringThe key to clear**Returns:** `true` on success, `false` on failure.

Usage Examples
--------------

[](#usage-examples)

### Common imports

[](#common-imports)

```
use Predis\Client;
use RateLimiter\Enum\CacheEnum;
use RateLimiter\Service\AbstractRateLimiterService;
```

---

### APCu

[](#apcu)

No external server required. Ideal for single-server deployments or CLI tools.

```
$limiter = AbstractRateLimiterService::factory(CacheEnum::APCU);
$key     = __METHOD__;
$limit   = 2;
$ttl     = 3;

if ($limiter->isLimited($key, $limit, $ttl)) {
    throw new \Exception("LIMIT REACHED: YOU SHALL NOT PASS!");
}
```

---

### Redis — Predis

[](#redis--predis)

Pure-PHP Redis client; no native extension required.

```
$redis = new Client([
    'scheme'     => 'tcp',
    'host'       => '192.168.0.100',
    'port'       => 6379,
    'persistent' => true,
]);

$limiter = AbstractRateLimiterService::factory(CacheEnum::REDIS, $redis);
$key     = __METHOD__;
$limit   = 2;
$ttl     = 3;

if ($limiter->isLimited($key, $limit, $ttl)) {
    throw new \Exception("LIMIT REACHED: YOU SHALL NOT PASS!");
}
```

---

### Redis — PhpRedis

[](#redis--phpredis)

Native `ext-redis` extension; better raw performance than Predis.

```
$redis = new \Redis();
$redis->pconnect(
    '192.168.0.100',
    6379,
    2,
    'persistent_id_rl'
);

$limiter = AbstractRateLimiterService::factory(CacheEnum::PHP_REDIS, $redis);
$key     = __METHOD__;
$limit   = 2;
$ttl     = 3;

if ($limiter->isLimited($key, $limit, $ttl)) {
    throw new \Exception("LIMIT REACHED: YOU SHALL NOT PASS!");
}
```

---

### Memcached

[](#memcached)

Requires `ext-memcached`. Passing a `persistent_id` reuses the connection pool across requests; the `getServerList()` guard prevents registering the same server twice.

```
$memcached = new \Memcached('persistent_id_rl');
if (!$memcached->getServerList()) {
    $memcached->addServer('192.168.0.100', 11211);
}

$limiter = AbstractRateLimiterService::factory(CacheEnum::MEMCACHED, $memcached);
$key     = __METHOD__;
$limit   = 2;
$ttl     = 3;

if ($limiter->isLimited($key, $limit, $ttl)) {
    throw new \Exception("LIMIT REACHED: YOU SHALL NOT PASS!");
}
```

---

### Rate Limit with Ban

[](#rate-limit-with-ban)

Use `isLimitedWithBan` when you want to progressively punish repeat offenders with longer block windows. The only difference between backends is the factory call — the parameters and behaviour are identical.

#### With APCu

[](#with-apcu)

```
$limiter = AbstractRateLimiterService::factory(CacheEnum::APCU);

$key          = __METHOD__;
$limit        = 5;
$ttl          = 60;
$maxAttempts  = 3;
$banTimeFrame = 300;
$banTtl       = 3600;
$clientIp     = $_SERVER['REMOTE_ADDR'] ?? null;

if ($limiter->isLimitedWithBan($key, $limit, $ttl, $maxAttempts, $banTimeFrame, $banTtl, $clientIp)) {
    throw new \RuntimeException("Too many login attempts. Please try again later.");
}
```

#### With Predis

[](#with-predis)

```
$redis = new Client([
    'scheme'     => 'tcp',
    'host'       => '192.168.0.100',
    'port'       => 6379,
    'persistent' => true,
]);

$limiter = AbstractRateLimiterService::factory(CacheEnum::REDIS, $redis);

$key          = __METHOD__;
$limit        = 5;
$ttl          = 60;
$maxAttempts  = 3;
$banTimeFrame = 300;
$banTtl       = 3600;
$clientIp     = $_SERVER['REMOTE_ADDR'] ?? null;

if ($limiter->isLimitedWithBan($key, $limit, $ttl, $maxAttempts, $banTimeFrame, $banTtl, $clientIp)) {
    throw new \RuntimeException("Too many login attempts. Please try again later.");
}
```

#### With PhpRedis

[](#with-phpredis)

```
$redis = new \Redis();
$redis->pconnect('192.168.0.100', 6379, 2, 'persistent_id_rl');

$limiter = AbstractRateLimiterService::factory(CacheEnum::PHP_REDIS, $redis);

$key          = __METHOD__;
$limit        = 5;
$ttl          = 60;
$maxAttempts  = 3;
$banTimeFrame = 300;
$banTtl       = 3600;
$clientIp     = $_SERVER['REMOTE_ADDR'] ?? null;

if ($limiter->isLimitedWithBan($key, $limit, $ttl, $maxAttempts, $banTimeFrame, $banTtl, $clientIp)) {
    throw new \RuntimeException("Too many login attempts. Please try again later.");
}
```

#### With Memcached

[](#with-memcached)

```
$memcached = new \Memcached('persistent_id_rl');
if (!$memcached->getServerList()) {
    $memcached->addServer('192.168.0.100', 11211);
}

$limiter = AbstractRateLimiterService::factory(CacheEnum::MEMCACHED, $memcached);

$key          = __METHOD__;
$limit        = 5;
$ttl          = 60;
$maxAttempts  = 3;
$banTimeFrame = 300;
$banTtl       = 3600;
$clientIp     = $_SERVER['REMOTE_ADDR'] ?? null;

if ($limiter->isLimitedWithBan($key, $limit, $ttl, $maxAttempts, $banTimeFrame, $banTtl, $clientIp)) {
    throw new \RuntimeException("Too many login attempts. Please try again later.");
}
```

#### Understanding `$banTimeFrame`

[](#understanding-bantimeframe)

`$banTimeFrame` is the **observation window** that determines how long a violation is "remembered". It answers the question: *"How many times has this client exceeded the limit in the last N seconds?"*.

```
$ttl          → How long each rate-limit window lasts (normal behaviour)
$banTimeFrame → How long violations are tracked (observation window)
$banTtl       → How long a ban lasts once the client is flagged

```

The violation counter is a fixed window starting at the **first** violation:

- It does **not** reset on each new violation (no sliding window).
- After `$banTimeFrame` seconds it expires and the client is "forgiven".

**Visual example** — `$limit=5, $ttl=60s, $maxAttempts=3, $banTimeFrame=300s, $banTtl=3600s`:

```
 t=0s     6 rapid requests → 5 allowed, 1 BLOCKED  → violation #1 (counter TTL = 300s)
 t=60s    Window resets. 6 requests again           → violation #2
 t=120s   Window resets. 6 requests again           → violation #3  ← ban threshold!
           violation_count = 3 >= maxAttempts=3
 t=180s   Window resets. Client tries again:
           violation_count still alive (expires at t≈300s)
           → ban applied: new window is 3600s instead of 60s
           → client blocked for 1 hour
 t=300s   Violation counter expires (banTimeFrame elapsed from t=0)
 t=3780s  Ban window expires (t=180 + banTtl=3600)
 t=3780s  Client can try again with a fresh violation counter

```

#### Per-client isolation with `$clientIp`

[](#per-client-isolation-with-clientip)

When `$clientIp` is provided, each IP address has its own independent violation counter. Banning `192.168.1.1` has no effect on `192.168.1.2`:

```
// Client A: banned after 3 violations
$limiter->isLimitedWithBan($key, $limit, $ttl, $maxAttempts, $banTimeFrame, $banTtl, '192.168.1.1');

// Client B: unaffected, starts from zero violations
$limiter->isLimitedWithBan($key, $limit, $ttl, $maxAttempts, $banTimeFrame, $banTtl, '192.168.1.2');
```

Pass `null` to use a **shared global counter** for the key (all clients contribute to the same violation count — useful when you want to protect a resource globally regardless of origin).

---

### Clearing a Rate Limit Key

[](#clearing-a-rate-limit-key)

`clearRateLimitedKey` resets the counter for a given key immediately. Useful after a successful authentication, a manual unban, or during testing.

```
// Works identically for every backend — swap the factory call as needed.
$limiter = AbstractRateLimiterService::factory(CacheEnum::APCU);

$key = 'App\Controller\LoginController::login';

if ($limiter->clearRateLimitedKey($key)) {
    // Counter reset; the next request will be treated as the first in a new window.
}
```

When using `isLimitedWithBan`, only the request counter is cleared by this method. The violation counter (used to track bans) lives under a separate internal key and is managed automatically by the library.

---

Development
-----------

[](#development)

### Dev dependencies

[](#dev-dependencies)

PackageVersionPurposephpunit/phpunit^12.1test runnerphpstan/phpstan^2.1static analysisfriendsofphp/php-cs-fixer^3.90code stylerector/rector^2.1automated refactoring### Available Scripts

[](#available-scripts)

CommandDescription`composer test`Run PHPUnit tests`composer phpstan`Run PHPStan static analysis`composer cs-fix`Fix code style with PHP-CS-Fixer`composer cs-check`Check code style (dry-run)`composer rector`Run Rector refactoring`composer rector-dry`Preview Rector changes`composer quality`Run all quality tools (Rector + CS-Fixer)`composer quality-check`Check quality without changesLicense
-------

[](#license)

This project is licensed under the GPL-3.0-or-later License - see the [LICENSE](LICENSE) file for details.

Author
------

[](#author)

**Stefano Perrini** - [spinfo.it](https://www.spinfo.it)

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance89

Actively maintained with recent releases

Popularity26

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity69

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~169 days

Recently: every ~117 days

Total

9

Last Release

56d ago

PHP version history (3 changes)v1.0.0PHP &gt;=8.1

v1.0.4PHP &gt;=8.2

v1.0.7PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/160662d52c89da3351e2a94b3d354074bf30137477fc7586eeacc52e3cb44462?d=identicon)[snipershady](/maintainers/snipershady)

---

Top Contributors

[![snipershady](https://avatars.githubusercontent.com/u/20489856?v=4)](https://github.com/snipershady "snipershady (60 commits)")

---

Tags

banratelimitratelimiter

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/snipershady-ratelimiter/health.svg)

```
[![Health](https://phpackages.com/badges/snipershady-ratelimiter/health.svg)](https://phpackages.com/packages/snipershady-ratelimiter)
```

###  Alternatives

[grumpydictator/firefly-iii

Firefly III: a personal finances manager.

24.2k69.5k](/packages/grumpydictator-firefly-iii)[unopim/unopim

UnoPim Laravel PIM

10.8k2.5k](/packages/unopim-unopim)[blair2004/nexopos

The Free Modern Point Of Sale System build with Laravel, TailwindCSS and Vue.js.

1.2k2.4k](/packages/blair2004-nexopos)[sunspikes/php-ratelimiter

A framework agnostic rate limiter for PHP

79693.6k1](/packages/sunspikes-php-ratelimiter)[splitsoftware/split-sdk-php

Split SDK for PHP

161.4M2](/packages/splitsoftware-split-sdk-php)[ethercreative/yii2-ip-ratelimiter

Allow guest clients to be rate limited, using their IP as the identifier.

36147.9k1](/packages/ethercreative-yii2-ip-ratelimiter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
