DI and actions
The facade is the shortest route and the recommended default, not the only one. The same API is available as RoundlyConsulting\HttpClientRateLimits\RateLimitManager — the facade root, bound as a singleton — which you can inject through the constructor: an explicit dependency and no static calls.
There is no third, action-based entry point: the package has no action classes, because a limit is a middleware object the manager builds, not a use case. So there is no facade method → action table either.
Inject the manager
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\HttpClientRateLimits\RateLimitManager;
final class OrdersClient
{
public function __construct(private RateLimitManager $rateLimits) {}
public function orders(string $account): Response
{
return Http::withMiddleware($this->rateLimits->perSecond(5)->by($account))
->get('https://api.example.com/orders');
}
}// Every facade method exists on the injected manager:
$limit = $this->rateLimits->profile('github');
$seconds = $this->rateLimits->retryAfter($response);
$queued = $this->rateLimits->releasingJob($job)->perSecond(5);RateLimits::fake() returns a RateLimitsFake — a RateLimitManager subtype — and swaps it into the container, so an injected manager and Http::rateLimit() receive the fake as well.
Build a limit by hand
When you need full control, compose a RateLimit from a Limit, a Store and a Deferrer yourself:
use RoundlyConsulting\HttpClientRateLimits\Deferrer\SleepDeferrer;
use RoundlyConsulting\HttpClientRateLimits\Limit;
use RoundlyConsulting\HttpClientRateLimits\Limiter;
use RoundlyConsulting\HttpClientRateLimits\RateLimit;
use RoundlyConsulting\HttpClientRateLimits\Store\RedisStore;
$rateLimit = new RateLimit(new Limiter(
new Limit(key: 'acct-1', maxAttempts: 5, timespan: 'second'),
new RedisStore('default'),
new SleepDeferrer,
));
Http::withMiddleware($rateLimit)->get('https://api.example.com/things');A hand-built limit uses exactly the store and deferrer you pass: the configured defaults don’t apply to it, and neither do the fake’s shared store and non-sleeping deferrer.
Internal classes
RetryAfter is @internal — parse the header with RateLimits::retryAfter() (or $manager->retryAfter()) instead.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.