graham-campbell/guzzle-factory
A simple Guzzle factory with sensible defaults for PHP 7.4–8.5. Create ready-to-use clients in one call, customize the handler stack, and optionally enable best-effort transport sharing for handler-lifetime reuse.
Architecture fit. Guzzle Factory is a single-class, framework-agnostic construction helper — not an HTTP abstraction layer. GuzzleFactory::make() returns a stock GuzzleHttp\Client, so adopting it introduces no new interface, no new base class, and no vendor lock-in at the call site. For a Laravel codebase where Guzzle clients are constructed ad hoc across controllers, jobs, Artisan commands, and service classes, that is the right shape: it centralizes retry policy, timeout, base URI, and TLS defaults without changing the type application code depends on. It is explicitly not a replacement for Laravel's Http facade or a PSR-18 abstraction, and it should not be positioned as one. Treat it as a configurable client factory behind an internal seam.
Integration feasibility. Composer install, one class, one static entry point. Implementation effort is small — roughly 0.5–1 day for a container binding plus config profiles, and a further 2–5 days to migrate existing call sites depending on how many Guzzle clients are scattered through the codebase. Hard prerequisites are narrow and checkable: Guzzle ≥ 7.11 and PHP 7.4–8.5 (the package's own PHP support window is broader than most Laravel 9–11 deployments, so this is rarely a blocker). The real cost is inventory and governance, not code.
Technical risk. Functionally low, but four items need explicit sign-off before approval:
HandlerStack mutator callback plus a TransportSharing argument. Any existing internal wrapper built on the older handler-stack accessor must be rewritten. Confirm whether we are already on that removed API — if so, this is a migration, not an adoption.GuzzleHttp\TransportSharing::HANDLER_PREFER. Verify that constant and namespace exist in the pinned Guzzle 7.11 release and that behavior is genuinely "best effort" before designing around it. Low-severity, but it is the one advanced feature we would otherwise assume works.Also weigh the market signal: an overall score of 8.7 against an opportunity score of 55.25 says this is a well-executed package with limited strategic differentiation. The functionality is roughly 40 lines of code any team could write. We are buying maintenance and defaults discipline, not capability — and that changes the build-vs-buy threshold only if the seam keeps exit cost near zero.
Key questions to resolve before approval.
verify => false, hardcoded timeouts, or bespoke retry logic? This baseline doubles as the business case.config/services.php or a dedicated config file?Stack fit. Laravel 9–11, PHP 8.1+, Guzzle 7.11+. Bind the factory in a service provider — a singleton plus contextual bindings keyed by upstream service. Drive base URI, timeout, retry count, and retryable status codes from configuration. Because the injected type remains stock GuzzleHttp\Client, it composes cleanly with queues, commands, pipelines, and existing service classes, and it is trivially mockable by swapping a MockHandler-backed client into the container for tests.
The one non-negotiable design decision. Introduce a thin internal HttpClientFactory that calls GuzzleFactory::make() internally, and have application code depend on that seam — never on the vendor static method directly. This costs one small class and buys an exit ramp: if the package is abandoned or a major bump misbehaves, the change is a two-line edit behind the seam rather than a codebase-wide codemod. It is also the single place to enforce retry policy and to attach logging or tracing middleware through the handler-stack callback.
Migration path — strangler, not big-bang.
verify => false, hardcoded timeouts, and hardcoded base URIs.new GuzzleHttp\Client( and verify => false outside the seam so usage cannot re-diverge after the migration completes.Compatibility and sequencing. Confirm Guzzle 7.11 availability across every dependent service before adopting v8, and verify PHP versions on all deployment targets. Sequence strictly as audit → seam and config → pilot → tiered rollout → CI ratchet. Do not combine the v8 API migration with the first production rollout; land the seam behind the old behavior first, then flip the implementation, so any regression has a single suspect.
Maintenance. Near zero by design — one class, no transitive dependency beyond Guzzle itself, no configuration surface beyond what we add. The maintenance burden we take on is not in the package but in our profiles and seam, which we own either way. Budget a periodic review of the pinned constraint against upstream releases; the changelog shows a steady cadence with clean semver major bumps, so upgrade planning is predictable. Expect to spend more time on Guzzle's major releases than on this package's.
Support. Community-supported by a single maintainer with a published security policy and a Tidelift commercial channel. This is a realistic bus-factor-of-one scenario, but the blast radius is contained: the seam means we can vendor, fork, or replace the class in hours. Do not assume upstream will absorb our edge cases. For revenue-path consumers, either purchase Tidelift coverage or accept the seam as the sole mitigation — state which, explicitly, in the approval record.
Scaling. No meaningful scaling concern. Client construction is cheap and the singleton binding avoids repeated instantiation. The one scaling-adjacent lever is the opt-in transport sharing mode: enabling it improves connection reuse under high outbound volume, but it is described as best effort and shares handler lifetime, so it deserves a load test before use on high-throughput paths rather than being switched on by default. Retry defaults also interact with load — aggressive retries amplify pressure on a degraded upstream. Set explicit retry budgets per profile rather than inheriting defaults.
Failure modes. Ranked by likelihood and impact:
Ramp-up. Essentially none for engineers already familiar with Guzzle; the API is one static call. The learning curve is organizational, not technical: teams must learn to use the seam and the named config profiles instead of constructing clients directly, which is a habit change enforced by linting rather than documentation. Plan a short internal write-up covering the seam, the profile taxonomy, and the retry-policy rules, plus a single worked example per tier. Onboarding a new engineer to "which HTTP client do I use and how is it configured" should take minutes once the profiles are named well — and that naming is the highest-leverage deliverable of the whole effort.
How can I help you explore Laravel packages today?