hamburgscleanest/laravel-guzzle-throttle
Laravel Guzzle Throttle adds configurable rate limiting to outgoing Guzzle requests using Laravel's cache. It helps prevent API throttling errors by limiting requests per host, with simple middleware integration and customizable limits.
Architecture fit. The package occupies the outbound HTTP egress layer: it registers a Laravel service provider, publishes a config file with per-host/per-route rate rules, and binds a Guzzle middleware (from the upstream hamburgscleanest/guzzle-advanced-throttle) into the client's handler stack. Architecturally this is the right place to solve vendor rate limiting — one enforcement point for all outbound calls rather than scattered sleep() calls — but it is a wrapper around a wrapper, so the app inherits two layers of third-party behavior (Laravel binding + Guzzle middleware semantics) and two release cadences.
Integration feasibility. Mechanically simple: composer require, publish config, declare host rules, attach the middleware to the Guzzle handler stack. The friction is in attachment. Laravel's Http facade does not expose the handler stack directly in older versions, so wiring typically requires a custom client binding, Http::globalOptions(['handler' => ...]) (Laravel 10+), or bypassing the facade entirely. Any SDK that constructs its own Guzzle client (payment, CRM, LLM providers) will not inherit the throttle unless it accepts an injected handler — this is the single biggest scope gap to validate.
Technical risk.
INCR-style semantics. Confirm the package's increment is actually atomic — a read-then-write race silently over-permits.composer.json constraints almost certainly predate current Laravel — verify with composer require --dry-run before assuming viability.Key questions for the maintainer/vendor.
Stack fit. Best fit is a Laravel app that already routes outbound calls through Guzzle and has a shared Redis cache — the marginal cost is one config file. Poor fit for apps on Http facade only, apps with SDK-managed clients, or apps without a distributed cache. In those cases the package solves a narrower problem than the one you have.
Migration path. Do not adopt org-wide. Sequence it as a bounded spike:
OutboundRateLimiter interface so the dependency is swappable — this is the insurance policy if the package is abandoned.Compatibility. Check three constraints before anything else: PHP version, guzzlehttp/guzzle major, and illuminate/support range. If the package requires Guzzle 6 or Laravel ≤8, the integration cost jumps from hours to a fork-and-patch project — at which point evaluate a maintained alternative (Laravel's built-in RateLimiter composed with a custom Guzzle middleware, or a maintained throttle middleware) rather than resurrecting this one.
Sequencing. Spike (1 wk) → decision gate on build-vs-adopt → interface abstraction → staging shadow → canary → rollout by integration criticality. Hold the abstraction layer regardless of outcome; it is what makes the eventual replacement cheap.
Maintenance. Assume you own this code. With no dependents and no visible community, there is no one else filing the Guzzle 8 or Laravel 13 compatibility PR. Budget for a pinned version, a quarterly compatibility check, and an internal fork if the repo goes quiet. The upstream guzzle-advanced-throttle library is a second dependency to track — a wrapper is only as maintained as what it wraps.
Support. No vendor. Requires a named internal owner and a runbook covering: how to add/adjust a host rule, how to disable throttling during an incident, and how to diagnose "requests are slow" tickets that are actually throttle waits.
Scaling. The cache store becomes a shared hot path and a single point of correctness — size Redis for the counter write rate, set TTLs deliberately so stale counters don't permanently throttle a host, and confirm eviction policy won't silently reset limits. Decide explicitly whether limits are global (correct, requires shared cache) or per-node (cheap, multiplies your effective rate by node count). Blocking waits mean throughput is bounded by worker pool size, not by upstream capacity; queue workers doing batched outbound calls are the highest-risk consumer and may need dedicated pools.
Failure modes to design for.
Ramp-up. Low for the happy path — a mid-level engineer can wire it in half a day. The non-obvious cost is conceptual: handler-stack ordering, cache atomicity, and fail-open/fail-closed semantics are where teams get burned, and none of them are visible in the config file. Plan for a short design review before production, not a code review after.
How can I help you explore Laravel packages today?