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

v1.0.7(3mo ago)43.4kGPL-3.0-or-laterPHPPHP ^8.3

Since Sep 28Pushed 3mo 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 2w ago

READMEChangelog (8)Dependencies (10)Versions (11)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)

```
# APCu
apt-get install php8.3-apcu

# Redis (php-redis native extension)
apt-get install php8.3-redis

# Memcached (php-memcached native extension — note the 'd')
apt-get install php8.3-memcached

# Replace "8.3" with the PHP version installed on your system if newer.
```

### 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)

### Dependencies

[](#dependencies)

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

### APCu Example

[](#apcu-example)

```
class Foo
{
    public function controllerYouWantToRateLimit(): Response
    {
        $limiter = AbstractRateLimiterService::factory(CacheEnum::APCU);
        $key = __METHOD__;  // Name of the function you want to rate limit
        $limit = 2;         // Maximum attempts before the limit
        $ttl = 3;           // Time window in seconds

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

        // ... your code
    }
}
```

### Redis Example (Predis)

[](#redis-example-predis)

```
class Foo
{
    public function controllerYouWantToRateLimit(): Response
    {
        $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!");
        }

        // ... your code
    }
}
```

### Redis Example (PhpRedis)

[](#redis-example-phpredis)

```
class Foo
{
    public function controllerYouWantToRateLimit(): Response
    {
        $redis = new \Redis();
        $redis->pconnect(
            '192.168.0.100',        // host
            6379,                   // port
            2,                      // connect timeout
            'persistent_id_rl'      // persistent_id
        );

        $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!");
        }

        // ... your code
    }
}
```

### Memcached Example

[](#memcached-example)

Requires `ext-memcached` (`apt-get install php8.4-memcached`).

```
class Foo
{
    public function controllerYouWantToRateLimit(): Response
    {
        $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!");
        }

        // ... your code
    }
}
```

> **Note:** passing a `persistent_id` to `new \Memcached()` reuses the connection pool across requests. The `getServerList()` guard prevents adding the same server twice.

### Rate Limit with Ban

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

Use this when you want to progressively punish repeat offenders with longer block windows. Normal rate limiting resets every `$ttl` seconds. With ban support, a client that repeatedly triggers the limit within the `$banTimeFrame` observation window gets its block window extended to `$banTtl` seconds instead.

#### With Predis

[](#with-predis)

```
class LoginController
{
    public function login(): Response
    {
        $redis = new Client([
            'scheme' => 'tcp',
            'host'   => '192.168.0.100',
            'port'   => 6379,
            'persistent' => true,
        ]);

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

        $key          = __METHOD__;
        $limit        = 5;      // Allow 5 login attempts per window
        $ttl          = 60;     // Normal window: 60 seconds
        $maxAttempts  = 3;      // Ban after 3 violations within $banTimeFrame
        $banTimeFrame = 300;    // Observation window: count violations over 5 minutes
        $banTtl       = 3600;   // Punishment: block for 1 hour when banned
        $clientIp     = $_SERVER['REMOTE_ADDR'] ?? null;

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

        // ... authentication logic
    }
}
```

#### With APCu

[](#with-apcu)

```
class LoginController
{
    public function login(): Response
    {
        $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 TooManyRequestsException("Too many login attempts. Please try again later.");
        }

        // ... authentication logic
    }
}
```

#### 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

```

**`$clientIp` and per-client isolation**

When `$clientIp` is provided, each IP address has its own independent violation counter. This means 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).

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

50

—

FairBetter than 95% of packages

Maintenance82

Actively maintained with recent releases

Popularity26

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity68

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 ~182 days

Recently: every ~308 days

Total

8

Last Release

90d 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 (57 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.

23.8k69.4k](/packages/grumpydictator-firefly-iii)[sunspikes/php-ratelimiter

A framework agnostic rate limiter for PHP

75684.6k1](/packages/sunspikes-php-ratelimiter)[ethercreative/yii2-ip-ratelimiter

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

36143.6k1](/packages/ethercreative-yii2-ip-ratelimiter)[aeon-php/rate-limiter

Aeon rate limiter (throttling) library

10143.1k1](/packages/aeon-php-rate-limiter)[jaaulde/php-ipv4

PHP classes for working with IPV4 addresses and networks.

1034.8k](/packages/jaaulde-php-ipv4)

PHPackages © 2026

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