Download the PHP package 0xbliv/call-throttle without Composer
On this page you can find all versions of the php package 0xbliv/call-throttle. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package call-throttle
call-throttle
A distributed call rate limiter for PHP. It paces callback execution to a fixed rate (e.g. 4 per second) and enforces that rate across independent processes — multiple queue workers, cron jobs, CLI runs — that share a limiter id. Coordination state lives in a shared backend (Redis, file, or database), so every worker agrees on the global rate.
You wrap the work you want throttled in a callback; the limiter decides when it runs. By default it blocks until a slot is free and then runs it, returning whatever your callback returns.
Install
Requires PHP 8.2+.
How it works
The limiter uses the GCRA (Generic Cell Rate Algorithm), a smooth leaky-rate limiter. A limit
of count per period:
- allows an initial burst of up to
countcalls, then - paces subsequent calls one every
period / countseconds.
The entire per-limiter state is a single timestamp, updated atomically in the chosen backend, so concurrent workers coordinate correctly.
Modes
| Call | Slot free | Throttled |
|---|---|---|
run($cb) |
sleeps ≤ maxWait, runs, returns value |
throws RateLimitExceededException |
throwOnLimit()->run($cb) |
runs, returns value | throws immediately (no wait) |
attempt($cb) |
runs, returns value | returns null, callback not run, no wait |
Exceptions thrown by your callback propagate unchanged.
Immutability
Throttle::for() returns a builder — the only place the store and rate are set. build() freezes
it into a final readonly Throttle that exposes run() / attempt() plus the wait-policy copies
withMaxWait() / withThrowOnLimit() (which return a new instance) — but no store or rate
setters. A live throttler's store and rate can never be reset, so it is safe to reuse and inject.
Sharing a limiter across workers (the registry)
Different processes — even different codebases — that call the same API must share one limit, and they don't start in a known order. The wrong way is to restate the rate at every call site: two callers can disagree, silently corrupting the pacing.
Instead, bind the rate to the id once and register it. Registration provisions the rate into the
shared backend: the first process to register writes the definition; every later process, in any
order, adopts it from the store. A caller that registers a different rate for the same id gets
a LimiterConflictException — never silent drift. Call sites then reference the limiter by name
only.
limiter('id') returns the immutable Throttle bound to the registered rate. To vary wait policy
per caller (not the rate), use withMaxWait() / withThrowOnLimit() / attempt():
To change a rate on purpose, redefine('id', $newRate) overwrites the stored definition.
Every feature is demonstrated under examples/ (see its
examples/php/ and
drop-in Laravel snippets in examples/laravel/. For the shared-across-processes
demo, run examples/php/shared_workers/worker.php in
several terminals at once.
For a genuinely one-off, unshared limiter you can still configure inline with
Throttle::for('id')->allow(4)->per('second')->store($store)->run(...)— but for anything shared, register it.
Stores
- Redis — best for multi-host fleets; the reserve runs entirely in a Lua script.
- File — zero infrastructure; only coordinates processes on a shared filesystem/host.
- Database — reuse an existing DB. Call
$store->createSchema()once, or run the Laravel migration.
Laravel
The Laravel bridge is optional — no illuminate/* package is a hard dependency, so plain-PHP
users pull nothing extra. It supports Laravel 10, 11, 12 and 13 (enforced by a conflict rule on
illuminate/support < 10, so an unsupported version fails at composer time rather than at runtime).
The package auto-registers. Publish the config (and, for the database driver, the migration):
Pick the driver with CALL_THROTTLE_DRIVER=file|redis|database. The file driver needs no setup
(state is stored privately under storage/framework/call-throttle); redis and database reuse
your app's existing connections (REDIS_* / DB_*).
Define shared limiters once in config; the service provider registers them at boot:
A rate is count/period, where period is second · minute · hour · day (aliases s · min
· h · d) or a raw number of seconds like 100/60. There is no week/month keyword — use raw
seconds. Keywords are singular (minute, not minutes); an unknown one throws at boot.
Then reference them by name anywhere — no rate at the call site:
For an ad-hoc, unshared limiter you can still configure inline:
Development
Redis tests are skipped unless REDIS_URL is set (e.g. REDIS_URL=tcp://127.0.0.1:6379).
License
MIT.