hamburgscleanest/guzzle-advanced-throttle
Advanced Throttle adds flexible rate-limiting middleware to Guzzle, letting PHP and Laravel apps enforce multiple request limits, use cache or Redis storage, and avoid API overages with configurable buckets, per-host rules, and smooth request spacing. Ideal for reliable API clients.
Architecture fit. The package is a Guzzle middleware, which is the correct architectural seam for outbound rate control: throttling is a cross-cutting concern that should not leak into domain/service code. It composes into the Guzzle HandlerStack, so it applies uniformly to every request made through an instrumented client, regardless of which service class originates the call. The rule abstraction (multiple named rules, linear vs. header-driven rate-limit rules) and the pluggable cache strategy map cleanly onto the two things we actually need: (a) stay under third-party quotas, and (b) degrade to cached data when a quota is exhausted. For a Laravel codebase this fits the "API client wrapper" pattern well—one configured GuzzleHttp\Client per upstream, each with its own rule set and cache namespace.
Integration feasibility. Composer-installable, Laravel package auto-discovery, config-driven rules. The realistic effort is small: install, publish config, define rules per upstream, and register the middleware on the relevant client(s). The friction points are not in the package but in our app: Laravel's Http facade builds its own client, so we need a custom binding or a dedicated client class to get the middleware in the stack. Cache driver choice is a hard dependency—counters must live in a shared store (Redis/Memcached), not file or array, or the limits are per-process and effectively meaningless.
Technical risk. The dominant risk is not technical, it is lifecycle: zero dependents and an empty star count mean this is effectively a single-maintainer package with no downstream production evidence. We should assume we may have to own it. Secondary risks, in order: (1) counter atomicity—if the implementation does read-modify-write against the cache rather than an atomic increment, concurrent workers can overshoot the limit under load; (2) blocking behavior—if the middleware sleeps to respect a window, it holds a PHP-FPM worker or queue worker for the duration, which converts a rate-limit problem into a capacity problem; (3) opaque failure semantics—we need to know whether a cache outage fails open (requests flow, quota at risk) or closed (requests blocked), and which we want; (4) middleware ordering interactions with retry middleware—if retries are handled outside the throttle, retry attempts bypass the bucket and can blow the quota.
Key questions.
X-RateLimit-* / Retry-After headers, or only static config?composer audit, and is it compatible with our PHP and Guzzle versions?Stack fit. Laravel (8–10) + Guzzle 7 + Redis is the natural home; the package leans on Laravel's cache and container, so it is a poor fit for a non-Laravel or polyglot service. It is a good fit precisely where we have many small outbound integrations with modest, well-understood quotas and a tolerance for cached fallbacks.
Migration path. Do not retrofit existing clients wholesale. Introduce an internal UpstreamClient abstraction (a thin factory/wrapper around GuzzleHttp\Client) so the middleware is an implementation detail we can swap. Wrap one or two low-blast-radius integrations first, keep the existing client available behind a config flag, and only then migrate the high-volume integrations. If the package proves unmaintained or incorrect under concurrency, the exit is a ~150-line hand-rolled middleware in the same slot, or a composition of an existing rate-limiter middleware plus a cache middleware—so the abstraction is the real insurance policy, not the package choice.
Compatibility. Verify PHP constraint, Guzzle major version, and Laravel auto-discovery against the lockfile before committing. Confirm the chosen cache store supports the primitives the package uses (increment, TTL). Audit middleware ordering explicitly: throttle should sit so that retries and redirects pass back through it, otherwise the bucket undercounts. Test alongside any existing retry/backoff and logging middleware.
Sequencing.
Maintenance. Treat upstream as dormant: pin the version exactly, run composer audit in CI, and budget a small recurring cost to vendor/fork it into an internal namespace (MIT-style licensing permitting) so a Guzzle or PHP upgrade doesn't strand us. The source is small enough to read end-to-end, which is the main mitigation.
Support. No vendor support and a thin community; internal expertise is the only support channel. Expect one engineer to become the de facto owner. Documentation beyond the README is our responsibility—particularly rule semantics and failure behavior, which are the things on-call will need at 3 a.m.
Scaling. Scaling behavior hinges on the cache: Redis-backed counters scale horizontally, but watch key cardinality and TTL hygiene (one key per rule per client per tenant can multiply). Sleeping middleware does not scale with request volume—it scales with worker count—so bulk or batch workloads must move to queued jobs with concurrency limits rather than relying on in-request sleeps. Clock-skew around reset windows is a subtle scaling failure mode; prefer TTL-driven windows over wall-clock comparisons.
Failure modes.
Ramp-up. A mid-level Laravel engineer can read the source and be productive in half a day; add a day for the operational scaffolding (dashboards, alerts, runbook) and a day for the concurrency validation. Total onboarding is under a week, which is disproportionately cheap relative to the lifecycle risk—the cost is ownership and attention, not learning curve.
How can I help you explore Laravel packages today?