maatify / rate-limiter
PSR-compliant rate limiter with Redis, MongoDB, and MySQL support.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/maatify/rate-limiter
Requires
- php: >=8.4
- ext-mongodb: *
- ext-pdo: *
- ext-pdo_mysql: *
- ext-redis: *
- maatify/psr-logger: ^1.0
- mongodb/mongodb: ^2.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2025-11-07 17:47:57 UTC
README
π§© Maatify Rate Limiter
A PSR-compliant Rate Limiter library supporting Redis, MongoDB, and MySQL β with dynamic driver resolver, middleware integration, and reusable enum contracts.
π Ψ¨Ψ§ΩΨΉΨ±Ψ¨Ω
β Completed Phases
- Phase 1 β Environment Setup (Local)
- Phase 2 β Core Architecture
- Phase 3 β Storage Drivers
- Phase 3.1 β Enum Contracts Refactor
- Phase 4 β Resolver & Middleware
- Phase 4.1 β Continuous Integration (Docker + GitHub Actions)
- Phase 5 β Exponential Backoff & Global Limit
βοΈ Local Setup
composer install cp .env.example .env
Then edit .env to match your local database and driver configuration.
π§ Description
Maatify Rate Limiter provides a unified abstraction for distributed rate limiting with multiple backends (Redis, MongoDB, MySQL) and dynamic resolver support.
It follows PSR-12, PSR-15, and PSR-7 standards, and can be integrated directly with frameworks like Slim or Laravel.
π Project Structure
maatify-rate-limiter/
β
βββ .env.example
βββ composer.json
βββ .github/
β βββ workflows/
β βββ ci.yml
βββ docker-compose.ci.yml
βββ src/
β βββ Config/
β β βββ RateLimitConfig.php
β βββ Contracts/
β β βββ RateLimiterInterface.php
β β βββ RateLimitActionInterface.php
β β βββ PlatformInterface.php
β βββ DTO/
β β βββ RateLimitStatusDTO.php
β βββ Drivers/
β β βββ RedisRateLimiter.php
β β βββ MongoRateLimiter.php
β β βββ MySQLRateLimiter.php
β βββ Enums/
β β βββ RateLimitActionEnum.php
β β βββ PlatformEnum.php
β βββ Exceptions/
β β βββ TooManyRequestsException.php
β βββ Middleware/
β β βββ RateLimitHeadersMiddleware.php
β βββ Resolver/
β βββ RateLimiterResolver.php
β
βββ tests/
β βββ bootstrap.php
β βββ CoreStructureTest.php
β βββ DriversTest.php
β βββ MiddlewareTest.php
β
βββ docs/
β βββ phases/
β βββ README.phase1.md
β βββ README.phase2.md
β βββ README.phase3.md
β βββ README.phase3.1.md
β βββ README.phase4.md
β βββ README.phase4.1.md
β
βββ CHANGELOG.md
βββ VERSION
βββ README.md
π§© CI/CD Integration (Phase 4.1)
π Phase 4.1 introduced full Continuous Integration support via Docker Compose + GitHub Actions.
- CI runs Redis, MySQL, and MongoDB containers in isolation.
- PHPUnit runs inside Docker (
docker compose run --rm php) with live console output. - Auto
.envgeneration during pipeline. - Composer caching for faster re-runs.
- Optional upload of test results (
tests/_output).
π‘ CI Workflow File: .github/workflows/ci.yml
π‘ Docker Stack File: docker-compose.ci.yml
π§© Current Version
1.0.0-alpha-phase5
π§Ύ CHANGELOG SUMMARY
Phase 4.1 β Continuous Integration (CI)
- Added Docker-based CI with
docker-compose.ci.yml. - Added GitHub Actions workflow
.github/workflows/ci.yml. - Integrated Redis 7, MySQL 8, and MongoDB 7 containers.
- Enabled live PHPUnit output inside CI logs.
- Automated
.envcreation and Composer caching. - Added artifact upload for test results.
- Completed full integration test environment.
Phase 5 β Exponential Backoff & Global Limit
- Added adaptive rate-limiting using exponential backoff (2βΏ logic).
- Added global per-IP rate limit (across all actions).
- Extended
RateLimitStatusDTOto includebackoffSecondsandnextAllowedAt. - Added
RateLimitStatusDTO::fromArray()for DTO reconstruction. - Enhanced
TooManyRequestsExceptionto include retry metadata. - Updated
.env.examplewith:GLOBAL_RATE_LIMITGLOBAL_RATE_WINDOWBACKOFF_BASEBACKOFF_MAX
- Added new unit tests
tests/BackoffTest.php. - Implemented global per-IP rate tracking for Redis.
β Summary Table
| Environment | Supported | Notes |
|---|---|---|
| PHP (raw) | β | Works out of the box |
| Slim | β | Fully PSR-15 compatible |
| Laravel | β | Custom middleware ready |
| Custom Enums | β | Through interface contracts |
| Redis / Mongo / MySQL | β | Switch easily via resolver |
| PSR Standards | β | PSR-7 / PSR-15 / PSR-12 |
π USAGE EXAMPLES
π§± 1οΈβ£ Basic Example (Native PHP)
<?php require 'vendor/autoload.php'; use Maatify\RateLimiter\Resolver\RateLimiterResolver; use Maatify\RateLimiter\Enums\RateLimitActionEnum; use Maatify\RateLimiter\Enums\PlatformEnum; use Maatify\RateLimiter\Exceptions\TooManyRequestsException; $config = [ 'driver' => 'redis', 'redis_host' => '127.0.0.1', 'redis_port' => 6379, ]; $resolver = new RateLimiterResolver($config); $limiter = $resolver->resolve(); $key = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; try { $status = $limiter->attempt($key, RateLimitActionEnum::LOGIN, PlatformEnum::WEB); echo "β Allowed. Remaining: {$status->remaining}\n"; } catch (TooManyRequestsException $e) { echo "β {$e->getMessage()}. Try again later.\n"; }
βοΈ 2οΈβ£ Slim Framework Example (Full Middleware Integration)
use Slim\Factory\AppFactory; use Maatify\RateLimiter\Resolver\RateLimiterResolver; use Maatify\RateLimiter\Middleware\RateLimitHeadersMiddleware; use Maatify\RateLimiter\Enums\RateLimitActionEnum; use Maatify\RateLimiter\Enums\PlatformEnum; require __DIR__ . '/vendor/autoload.php'; $app = AppFactory::create(); $config = [ 'driver' => 'redis', 'redis_host' => '127.0.0.1', ]; $resolver = new RateLimiterResolver($config); $limiter = $resolver->resolve(); $app->add(new RateLimitHeadersMiddleware( $limiter, RateLimitActionEnum::LOGIN, PlatformEnum::WEB )); $app->get('/login', function ($request, $response) { $response->getBody()->write('Welcome to login endpoint!'); return $response; }); $app->run();
π Output Headers:
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 60
Retry-After: 60
π§© 3οΈβ£ Laravel Example (Custom Middleware)
π app/Http/Middleware/RateLimitHeaders.php
<?php namespace App\Http\Middleware; use Closure; use Maatify\RateLimiter\Resolver\RateLimiterResolver; use Maatify\RateLimiter\Enums\RateLimitActionEnum; use Maatify\RateLimiter\Enums\PlatformEnum; use Maatify\RateLimiter\Exceptions\TooManyRequestsException; class RateLimitHeaders { public function handle($request, Closure $next) { $config = ['driver' => 'redis', 'redis_host' => '127.0.0.1']; $resolver = new RateLimiterResolver($config); $limiter = $resolver->resolve(); $key = $request->ip(); try { $status = $limiter->attempt($key, RateLimitActionEnum::API_CALL, PlatformEnum::API); } catch (TooManyRequestsException $e) { return response()->json([ 'error' => 'Too many requests', 'retry_after' => $status->retryAfter ?? 60, ], 429); } $response = $next($request); return $response ->header('X-RateLimit-Limit', $status->limit) ->header('X-RateLimit-Remaining', $status->remaining) ->header('X-RateLimit-Reset', $status->resetAfter); } }
π Register in Kernel.php:
'ratelimit' => \App\Http\Middleware\RateLimitHeaders::class,
Usage:
Route::get('/api/orders', [OrderController::class, 'index'])->middleware('ratelimit');
π 4οΈβ£ API JSON Example (Custom Controller)
<?php use Maatify\RateLimiter\Resolver\RateLimiterResolver; use Maatify\RateLimiter\Enums\RateLimitActionEnum; use Maatify\RateLimiter\Enums\PlatformEnum; use Maatify\RateLimiter\Exceptions\TooManyRequestsException; $config = ['driver' => 'mysql', 'mysql_dsn' => 'mysql:host=127.0.0.1;dbname=ratelimiter', 'mysql_user' => 'root']; $resolver = new RateLimiterResolver($config); $limiter = $resolver->resolve(); header('Content-Type: application/json'); $key = $_SERVER['REMOTE_ADDR']; try { $status = $limiter->attempt($key, RateLimitActionEnum::API_CALL, PlatformEnum::API); echo json_encode([ 'success' => true, 'remaining' => $status->remaining, 'reset_after' => $status->resetAfter, ]); } catch (TooManyRequestsException $e) { http_response_code(429); echo json_encode([ 'success' => false, 'error' => $e->getMessage(), 'retry_after' => $status->retryAfter ?? 60, ]); }
π§ 5οΈβ£ Custom Enum Contracts Example (From Phase 3.1)
use Maatify\RateLimiter\Contracts\RateLimitActionInterface; use Maatify\RateLimiter\Contracts\PlatformInterface; use Maatify\RateLimiter\Resolver\RateLimiterResolver; enum MyActionEnum: string implements RateLimitActionInterface { case ORDER_SUBMIT = 'order_submit'; public function value(): string { return $this->value; } } enum MyPlatformEnum: string implements PlatformInterface { case CUSTOMER_APP = 'customer_app'; public function value(): string { return $this->value; } } $config = ['driver' => 'redis']; $resolver = new RateLimiterResolver($config); $limiter = $resolver->resolve(); $status = $limiter->attempt('user-501', MyActionEnum::ORDER_SUBMIT, MyPlatformEnum::CUSTOMER_APP); echo json_encode($status->toArray(), JSON_PRETTY_PRINT);
π§© 6οΈβ£ Custom Header Key Example (X-API-KEY Mode)
$app->add(new RateLimitHeadersMiddleware( $limiter, RateLimitActionEnum::API_CALL, PlatformEnum::API, keyHeader: 'X-API-KEY' ));
π§ 7οΈβ£ Exponential Backoff Example (Redis Adaptive Mode)
use Maatify\RateLimiter\Resolver\RateLimiterResolver; use Maatify\RateLimiter\Enums\RateLimitActionEnum; use Maatify\RateLimiter\Enums\PlatformEnum; use Maatify\RateLimiter\Exceptions\TooManyRequestsException; $config = [ 'driver' => 'redis', 'redis_host' => '127.0.0.1', 'redis_port' => 6379, 'backoff_base' => 2, 'backoff_max' => 3600, ]; $resolver = new RateLimiterResolver($config); $limiter = $resolver->resolve(); $key = 'ip:' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'); try { $status = $limiter->attempt($key, RateLimitActionEnum::LOGIN, PlatformEnum::WEB); echo "β Allowed β Remaining: {$status->remaining}"; } catch (TooManyRequestsException $e) { echo "β Retry after {$status->backoffSeconds}s (next allowed: {$status->nextAllowedAt})"; }
π Environment variables:
GLOBAL_RATE_LIMIT=1000 GLOBAL_RATE_WINDOW=3600 BACKOFF_BASE=2 BACKOFF_MAX=3600
βοΈ Environment Variables (Rate Limiter Configuration)
These variables control the global rate limit and exponential backoff behavior
used across all actions and IP addresses.
You can define them in your .env, .env.local, or CI environment.
| Variable | Description | Example | Type |
|---|---|---|---|
GLOBAL_RATE_LIMIT |
Maximum number of allowed actions per IP (or user) within the window duration. | 5 |
integer |
GLOBAL_RATE_WINDOW |
Duration of the rate-limit window in seconds. After this period, counters reset. | 60 (1 minute) |
integer |
BACKOFF_BASE |
Exponential backoff multiplier. Each violation increases delay exponentially by this base value. | 2 β 2, 4, 8, 16... |
integer / float |
BACKOFF_MAX |
Maximum delay (in seconds) allowed by exponential backoff. Prevents unreasonably long waits. | 3600 (1 hour) |
integer |
π Formula:
backoff_seconds = min( BACKOFF_BASE ** violation_count , BACKOFF_MAX )
π Example .env file
# Basic local testing GLOBAL_RATE_LIMIT=5 GLOBAL_RATE_WINDOW=60 BACKOFF_BASE=2 BACKOFF_MAX=3600
π‘ Tips
- Lower values (e.g., 5 req/min) are recommended for login or OTP endpoints.
- Higher values are suitable for public APIs.
BACKOFF_BASE=2gives a balanced exponential delay pattern.- Always include a
Retry-Afterheader in responses to inform the client when to retry.
π§± Example exponential pattern:
| Violation Count | Delay (seconds) |
|---|---|
| 1 | 2 |
| 2 | 4 |
| 3 | 8 |
| 4 | 16 |
| 5 | 32 |
| 6 | 64 |
| ... | ... up to BACKOFF_MAX |
π¦ Composer Dependencies
To use this library fully:
composer require psr/http-message psr/http-server-middleware psr/http-server-handler
For Slim Framework support:
composer require slim/slim
πͺͺ License
Youβre free to use, modify, and distribute this library with attribution.
π§± Authors & Credits
Developed by: Maatify.dev https://www.Maatify.dev
Maintainer: Mohamed Abdulalim
Project: maatify:rate-limiter