Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Stiphle Laravel Package

davedevelopment/stiphle

Stiphle is a small PHP library that turns any string into a clean, URL-friendly unique identifier. It slugifies text and automatically resolves collisions by appending numbers, making it handy for generating unique slugs, IDs, or filenames from titles and names.

Deep Wiki
Context7

Getting Started

composer require davedevelopment/stiphle

Minimal, single-process usage:

use Stiphle\Throttle\LeakyBucket;

$throttle = new LeakyBucket;

// Allow 5 calls per 1000ms, keyed by API token
$wait = $throttle->getThrottle('api:'.$token, 5, 1000);

if ($wait > 0) {
    usleep($wait * 1000); // returned value is milliseconds
}

That is the whole library surface you need on day one: getThrottle($key, $limit, $windowMs) returns 0 if you are under the limit, or the number of milliseconds you should wait before retrying.

Where to look first:

  • src/Stiphle/Throttle/LeakyBucket.php — the only throttle implementation worth reading; understand leak() and calculateWait().
  • src/Stiphle/Storage/ArrayStorage.php — default storage; useful for tests, useless across requests.
  • src/Stiphle/Storage/ — Redis/Memcached/Mongo adapters; check which one matches your Laravel cache driver.
  • src/Stiphle/Lock/ — the LockInterface used to protect the read-modify-write cycle.

First use case: throttling calls to a third-party API (Stripe, GitHub, an internal service) from a wrapper client, before you reach for a full queue-based backoff.

Implementation Patterns

Bind a configured throttle in a service provider. The default ArrayStorage resets every request, so in Laravel you must swap it for shared storage:

// app/Providers/AppServiceProvider.php
$this->app->singleton(LeakyBucket::class, function ($app) {
    $throttle = new LeakyBucket;
    $throttle->setStorage(new \Stiphle\Storage\Redis($app['redis']->connection()));
    $throttle->setLock($app->make(App\Throttle\RedisLock::class));
    return $throttle;
});

HTTP middleware for a single route group. Resolve the throttle, derive a key from user/IP/token, and return 429 with Retry-After when $wait > 0:

public function handle($request, Closure $next)
{
    $key = 'throttle:'.($request->user()?->id ?? $request->ip());
    $wait = $this->throttle->getThrottle($key, 60, 60000);

    if ($wait > 0) {
        return response('Too Many Requests', 429)
            ->header('Retry-After', ceil($wait / 1000));
    }

    return $next($request);
}

Outbound client decorator. Wrap each upstream call and sleep inline; this is where a leaky bucket shines versus RateLimiter::attempt, because it tells you how long to wait rather than just failing:

public function request(string $endpoint, array $payload)
{
    $key = "upstream:{$this->vendor}:{$this->accountId}";
    $wait = $this->throttle->getThrottle($key, 100, 60000);
    if ($wait > 0) {
        usleep($wait * 1000);
    }
    return $this->http->post($endpoint, $payload);
}

Queue worker throttling. Same call in a job's handle(). Because the leaky bucket is smooth rather than windowed, workers naturally spread out instead of bursting at second boundaries — usually what you want when hammering an upstream.

Namespace your keys relentlessly. "throttle:{$scope}:{$id}". Mixing IPs, user IDs and API keys in one namespace will silently cross-credit consumption.

Testing. Inject ArrayStorage and freeze time by manipulating the storage value directly, or just assert that getThrottle returns 0 up to the limit. Avoid real sleeps in the test suite.

Gotchas and Tips

  • Calling getThrottle consumes budget even when it returns a wait. A retry loop that calls getThrottle, ignores the wait, and calls again will dig the bucket deeper. Always usleep/sleep the returned milliseconds, or abandon the request.
  • The default ArrayStorage is per-request only. Every web request starts from zero. If your rate limiting "doesn't work" in production but works in a single CLI script, this is why.
  • No lock by default = race conditions. LeakyBucket reads, mutates and writes the bucket; under concurrency you undercount. Supply a LockInterface implementation backed by the same store (Redis SET NX PX, Memcached add, database advisory lock). Storage adapters do not lock for you.
  • Argument order is key, limit, windowMs. It reads as "5 calls per 1000ms". Mixing up count and window is the most common first bug.
  • Values are floats, not integers. The leak subtracts fractional amounts between calls, so your storage must preserve floats. Redis stringified values and Memcached integer flags can round — verify round-tripping with a quick script before trusting counts under load.
  • The clock is microtime() internally; there is no injectable clock. Tests and clock-skewed containers can behave oddly. Keep app hosts NTP-synced; don't test throttle timing by racing real seconds.
  • Key expiry is your problem. The library does not set TTLs on idle buckets. On Redis you will accumulate keys for every user/IP ever seen. Add periodic cleanup or a custom storage decorator that expires keys after the window elapses.
  • Identifier hygiene. Include the route or upstream name in the key; otherwise one noisy endpoint drains the budget for the whole user.
  • Laravel ≥ 5.6 ships Illuminate\Cache\RateLimiter. If you need classic fixed windows and framework-native middleware, use that instead; reach for Stiphle when you specifically want smooth leaky-bucket pacing or a "how long to sleep" answer.
  • Extension points are small and clean: implement Stiphle\Storage\StorageInterface (get/set/lock/unlock) to back the bucket with your own store, or Stiphle\Lock\LockInterface for atomicity. Wrapping LeakyBucket in a decorator for logging/metrics is safe — it is not final.
  • Check PHP compatibility against your Laravel version. The package has a long history; pin a constraint that matches the PHP version your app actually runs, and smoke-test the chosen storage adapter after every upgrade.
  • Debugging tip: pick one key and dump its raw value from Redis before and after a getThrottle call. Watching the counter leak downward over time is the fastest way to build a correct mental model — and to spot a storage adapter that is silently truncating floats.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core