PHPackages                             sanmai/rate-limiter - 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. [Caching](/categories/caching)
4. /
5. sanmai/rate-limiter

ActiveLibrary[Caching](/categories/caching)

sanmai/rate-limiter
===================

Cache-based rate limiter using sliding window algorithm

0.1.4(4mo ago)89.3k↓20.1%Apache-2.0PHPPHP ^8.2CI passing

Since Apr 11Pushed 4mo ago1 watchersCompare

[ Source](https://github.com/sanmai/rate-limiter)[ Packagist](https://packagist.org/packages/sanmai/rate-limiter)[ RSS](/packages/sanmai-rate-limiter/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (11)Versions (6)Used By (0)

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

[](#rate-limiter)

[![Latest Stable Version](https://camo.githubusercontent.com/bd1f313b5ec4af4b31e0c72e930bf54d73e25a47fdd71d457f57ef74ee68b801/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73616e6d61692f726174652d6c696d697465722e737667)](https://packagist.org/packages/sanmai/rate-limiter)

Cache-based API rate limiting for PHP applications.

Table of Contents
-----------------

[](#table-of-contents)

- [Overview](#overview)
- [Features](#features)
- [How It Works](#how-it-works-the-simple-version)
- [Installation](#installation)
- [Quick Start](#quick-start)
    - [Setting up a rate limiter](#setting-up-a-rate-limiter)
    - [Tracking requests](#tracking-requests)
    - [Checking rate limits](#checking-rate-limits)
    - [Getting more information](#getting-more-information)
- [Advanced Usage](#advanced-usage)
- [Cache Adapters](#cache-adapters)
- [Technical Details](#technical-details)
- [Contributing](#contributing)
- [License](#license)

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

[](#installation)

```
composer require sanmai/rate-limiter
```

Overview
--------

[](#overview)

Rate limiting with the sliding window approach.

Real-world example: Imagine you need to limit API requests to 100 per minute and 1000 per hour per client. This library lets you create a rate limiter with a 1-minute window and 1-hour observation period, then check if a client exceeds either of these limits.

Features
--------

[](#features)

- Two-level limiting - Window-based and period-based limits
- Lazy evaluation - Calculates limits only when needed
- PSR-compatible - Easily integrates with PSR-15 middleware

How it works (the simple version)
---------------------------------

[](#how-it-works-the-simple-version)

This rate limiter provides two types of limits:

1. Window limits - Controls request rates in the most recent time window (e.g., 100 requests per minute)
2. Period limits - Controls total requests over a longer observation period (e.g., 1000 requests per hour)

The rate limiter itself tracks requests, while the limits are set when checking if they've been exceeded.

Quick Start
-----------

[](#quick-start)

### Setting up a rate limiter

[](#setting-up-a-rate-limiter)

```
// Import necessary classes
use SlidingWindowCounter\RateLimiter\RateLimiter;
use SlidingWindowCounter\Cache\MemcachedAdapter;

// Create a rate limiter for an IP address with 1-minute windows and 1-hour observation period
$rateLimiter = RateLimiter::create(
    '192.168.1.1',          // Subject being rate limited (e.g., IP address)
    'api_requests',         // Name for your rate limiter
    60,                     // Window size: 60 seconds (1 minute)
    3600,                   // Observation period: 3600 seconds (1 hour)
    new MemcachedAdapter($memcached)
);
```

### Tracking requests

[](#tracking-requests)

```
// Record a request from this client
$rateLimiter->increment();

// You can also increment by a specific amount (for weighted actions)
$rateLimiter->increment(2); // Count this action as 2 requests
```

### Checking rate limits

[](#checking-rate-limits)

```
// Check if the client has exceeded window limit (100 requests per minute)
$windowResult = $rateLimiter->checkWindowLimit(100);

if ($windowResult->isLimitExceeded()) {
    // Window limit exceeded - client is sending requests too quickly
    echo $windowResult->getLimitExceededMessage();
    // Example output: "Rate limit exceeded for 192.168.1.1: 120 actions in the window (limit: 100)"

    // Return 429 Too Many Requests response
    header('HTTP/1.1 429 Too Many Requests');
    header(sprintf('Retry-After: %d', $windowResult->getWaitTimeSeconds()));
    exit;
}

// Check if the client has exceeded period limit (1000 requests per hour)
$periodResult = $rateLimiter->checkPeriodLimit(1000);

if ($periodResult->isLimitExceeded()) {
    // Period limit exceeded - client has sent too many requests in the observation period
    echo $periodResult->getLimitExceededMessage();

    // Return 429 Too Many Requests response
    header('HTTP/1.1 429 Too Many Requests');
    header(sprintf('Retry-After: %d', $periodResult->getWaitTimeSeconds()));
    exit;
}
```

### Getting more information

[](#getting-more-information)

```
// Get information about the current rate limit status
$windowResult = $rateLimiter->checkWindowLimit(100);

// Subject being rate limited
$subject = $windowResult->getSubject(); // e.g., "192.168.1.1"

// Current count in the window
$count = $windowResult->getCount();

// Maximum limit
$limit = $windowResult->getLimit();

// Type of limit
$limitType = $windowResult->getLimitType(); // "window" or "period"

// Get the limit message (only if exceeded)
$message = $windowResult->getLimitExceededMessage();

// Get wait time in seconds (rounded up) - useful for Retry-After header
$waitSeconds = $windowResult->getWaitTimeSeconds();

// Get wait time in nanoseconds - useful for precise sleeping
$waitNanoseconds = $windowResult->getWaitTime();

// Get wait time with jitter to avoid thundering herd (0.5 = up to 50% extra delay)
$waitWithJitter = $windowResult->getWaitTime(0.5);

// Get the latest value in the current window
$currentValue = $rateLimiter->getLatestValue();

// Get the total across all windows in the observation period
$totalRequests = $rateLimiter->getTotal();
```

Advanced Usage
--------------

[](#advanced-usage)

### Using multiple rate limiters for different constraints

[](#using-multiple-rate-limiters-for-different-constraints)

You can create different rate limiters for different types of constraints:

```
// General rate limiter with 1-minute windows and 1-hour observation period
$generalLimiter = RateLimiter::create($clientIp, 'general_api', 60, 3600, $cache);

// Check if client exceeds 100 requests per minute
$windowResult = $generalLimiter->checkWindowLimit(100);

// Check if client exceeds 1000 requests per hour
$periodResult = $generalLimiter->checkPeriodLimit(1000);

// Stricter limiter for sensitive endpoints with same time parameters
$sensitiveLimiter = RateLimiter::create($clientIp, 'sensitive_api', 60, 3600, $cache);

// Check if client exceeds 10 requests per minute for sensitive endpoints
$sensitiveWindowResult = $sensitiveLimiter->checkWindowLimit(10);

// Check if client exceeds 50 requests per hour for sensitive endpoints
$sensitivePeriodResult = $sensitiveLimiter->checkPeriodLimit(50);
```

### Self-throttling for background jobs

[](#self-throttling-for-background-jobs)

When you control both ends (e.g., a background job calling your own API), you can use the wait time to self-throttle instead of failing:

```
use DuoClock\DuoClock;

$clock = new DuoClock();
$rateLimiter = RateLimiter::create($jobId, 'batch_processing', 60, 3600, $cache);

foreach ($items as $item) {
    $rateLimiter->increment();

    $result = $rateLimiter->checkWindowLimit(100);
    if ($result->isLimitExceeded()) {
        // Wait until the rate limit resets using DuoClock's nanosleep
        $clock->nanosleep($result->getWaitTime());
    }

    processItem($item);
}
```

If you're not using [DuoClock](https://github.com/sanmai/DuoClock), you can use PHP's `time_nanosleep()` directly:

```
$ns = $result->getWaitTime();
time_nanosleep(intdiv($ns, 1_000_000_000), $ns % 1_000_000_000);
```

When multiple workers compete for the same rate limit, use jitter to spread out retries and avoid thundering herd:

```
$result = $rateLimiter->checkWindowLimit(100);
if ($result->isLimitExceeded()) {
    // Add up to 50% random delay to spread out competing workers
    $clock->nanosleep($result->getWaitTime(0.5));
}
```

**Note on wait time calculation:** The wait time assumes a uniform distribution of requests across the window. If requests are bursty (clustered at the start or end of the window), the actual required wait time may differ. For most use cases this approximation works well.

### Implementing in middleware

[](#implementing-in-middleware)

Here's how you might implement rate limiting in a PSR-15 middleware:

```
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    $ip = $request->getServerParams()['REMOTE_ADDR'];

    // Create rate limiter
    $rateLimiter = RateLimiter::create($ip, 'api_requests', 60, 3600, $this->cache);

    // Increment the counter
    $rateLimiter->increment();

    // Check window limit (e.g., 100 requests per minute)
    $windowResult = $rateLimiter->checkWindowLimit(100);
    if ($windowResult->isLimitExceeded()) {
        return $this->createRateLimitResponse(
            $windowResult->getLimitExceededMessage(),
            $windowResult->getWaitTimeSeconds()
        );
    }

    // Check period limit (e.g., 1000 requests per hour)
    $periodResult = $rateLimiter->checkPeriodLimit(1000);
    if ($periodResult->isLimitExceeded()) {
        return $this->createRateLimitResponse(
            $periodResult->getLimitExceededMessage(),
            $periodResult->getWaitTimeSeconds()
        );
    }

    // Limits not exceeded, continue with the request
    return $handler->handle($request);
}
```

### Error Handling

[](#error-handling)

Here are some common scenarios and how to handle them:

```
try {
    // Create the rate limiter
    $rateLimiter = RateLimiter::create($ip, 'api_requests', 60, 3600, $cache);

    // Increment and check limits
    $rateLimiter->increment();
    $windowResult = $rateLimiter->checkWindowLimit(100);

    // Handle rate limit exceeded
    if ($windowResult->isLimitExceeded()) {
        // Log the rate limit event
        $this->logger->warning('Rate limit exceeded', [
            'ip' => $ip,
            'count' => $windowResult->getCount(),
            'limit' => $windowResult->getLimit(),
            'type' => $windowResult->getLimitType()
        ]);

        // Return appropriate response with calculated wait time
        return $this->createRateLimitResponse(
            $windowResult->getLimitExceededMessage(),
            $windowResult->getWaitTimeSeconds()
        );
    }
} catch (Exception $e) {
    // If the cache service is unavailable, fail open (allow the request)
    $this->logger->error('Rate limiter error', ['exception' => $e]);

    // Continue processing the request
    return $handler->handle($request);
}
```

Cache Adapters
--------------

[](#cache-adapters)

This library uses the cache adapters provided by the `sanmai/sliding-window-counter` library. For information about available adapters and how to create your own, please refer to the [sliding window counter documentation](https://github.com/sanmai/sliding-window-counter).

License
-------

[](#license)

[![License](https://camo.githubusercontent.com/0e170fe00db573d10e62d3a7d4a5c7a1355f0a7e49c9435c30097602c3f0897e/68747470733a2f2f706f7365722e707567782e6f72672f73616e6d61692f726174652d6c696d697465722f6c6963656e7365)](https://packagist.org/packages/sanmai/rate-limiter)

See the [LICENSE](LICENSE) file for details.

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance74

Regular maintenance activity

Popularity32

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

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

Total

5

Last Release

140d ago

PHP version history (2 changes)0.1PHP ^8.1

0.1.1PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/edcb8dde95c71b1c97c3c91e57d3548795fa2014c657744fb878e2be3b5949fc?d=identicon)[sanmai](/maintainers/sanmai)

---

Top Contributors

[![sanmai](https://avatars.githubusercontent.com/u/139488?v=4)](https://github.com/sanmai "sanmai (12 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sanmai-rate-limiter/health.svg)

```
[![Health](https://phpackages.com/badges/sanmai-rate-limiter/health.svg)](https://phpackages.com/packages/sanmai-rate-limiter)
```

###  Alternatives

[infection/infection

Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.

2.2k26.2M1.8k](/packages/infection-infection)[react/cache

Async, Promise-based cache interface for ReactPHP

444112.4M40](/packages/react-cache)[wp-media/wp-rocket

Performance optimization plugin for WordPress

7431.3M3](/packages/wp-media-wp-rocket)[illuminate/cache

The Illuminate Cache package.

12835.6M1.4k](/packages/illuminate-cache)[colinmollenhour/php-redis-session-abstract

A Redis-based session handler with optimistic locking

6325.6M14](/packages/colinmollenhour-php-redis-session-abstract)[cheprasov/php-redis-client

Php client for Redis. It is a fast, fully-functional and user-friendly client for Redis, optimized for performance. RedisClient supports the latest versions of Redis starting from 2.6 to 6.0

1281.2M21](/packages/cheprasov-php-redis-client)

PHPackages © 2026

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