friendsofouro/http-batch-contract
A PHP contract package for batching HTTP requests. It defines interfaces for grouping requests, handling batch responses, and keeping batch logic decoupled from implementation details, making it easier to build consistent, framework-agnostic HTTP batching across applications and services.
The v3.0.0 release is a breaking change to the ResponseBatchInterface contract, and it materially affects how this package integrates into a Laravel/PHP service. The changes are narrow in surface area (a single interface) but sit on the hot path of batch request handling, so any consumer that type-hints against the interface, calls the renamed methods, or relies on the old array return from filter() will break at runtime or — worse — fail static analysis before it fails in production.
Architecture fit. The package remains a reasonable fit for a Laravel app that fans out concurrent HTTP requests (e.g. multi-provider enrichment, bulk webhook dispatch, aggregate API calls). The v3 return-type refinements (ResponseInterface[], ClientExceptionInterface[]) push the package toward PSR-18/PSR-7-shaped semantics, which aligns well with Laravel's existing Guzzle/Http facade ecosystem and with strict typing conventions we would want at service boundaries. filter() returning static is a genuine ergonomic improvement for chained predicate logic, but it changes the contract from "returns a collection you iterate" to "returns a batch you continue operating on," which is a behavioral change, not just a signature change.
Integration feasibility. Integration is straightforward provided we are on v2.x or greenfield. The migration is mechanical: three call-site renames plus a re-typing of filter() consumers. The absence of prior assessment data means we have no recorded baseline for how many internal call sites depend on the old contract — this is the primary unknown and must be answered before we commit to a timeline.
Technical risk. Moderate. The risk is concentrated in (a) undiscovered call sites in less-traveled code paths (queued jobs, artisan commands, test doubles), (b) mocks and fakes that implement ResponseBatchInterface directly and will now violate the contract, and (c) any code that does array-native operations on filter()'s return (count(), array_map, foreach with index keys) that silently assumed array. The filter() change is the sleeper risk: it will not necessarily produce a loud type error in loosely typed consumer code, so it can fail quietly in production. Notably, the release notes are machine-generated ("Generated with Claude Code") and contain no deprecation shim, no @deprecated annotations, and no upgrade tooling — so the burden of safe migration is entirely ours.
Key questions.
getSuccessfulResults(), getFailedResults(), or filter() on a batch object? Do we have static analysis (PHPStan/Psalm) gating CI to surface these mechanically?implement ResponseBatchInterface? Those require hand-editing, not search-and-replace.composer.json that would let v3 land silently on composer update? What is our constraint today — ^2.0 or *?BatchItemInterface (which wrapped both success and failure) rather than the new split responses/exceptions? If so, that code needs restructuring, not renaming.Stack fit. The new typings improve fit with a strictly typed Laravel codebase. Returning ResponseInterface[] and ClientExceptionInterface[] means our service layer can consume standard PSR interfaces rather than package-specific wrappers, reducing coupling and making it easier to swap or stub the transport in tests. This is a net positive for a codebase already leaning on PSR-18 clients.
Migration path. Recommended path is a dedicated, time-boxed upgrade rather than opportunistic inclusion in unrelated work:
composer.json to ^2.0 (or lockfile-pin) to prevent an accidental v3 pull before we are ready. Confirm the current locked version.getSuccessfulResults, getFailedResults, and batch filter( usage; run PHPStan/Psalm at a strict level against the dependency to enumerate contract violations. Include tests, factories, and fakes.^3.0, apply renames, and critically audit every filter() consumer for array-vs-object assumptions. Convert count($filtered) / array_map patterns to batch-object patterns.Compatibility. No dual-version support should be attempted — the method names collide, so v2 and v3 cannot coexist in one process. This is a hard cutover. Any shared internal package that exposes batch objects across a boundary must be upgraded in lockstep with all its consumers; sequence that carefully.
Sequencing. Do the constraint pin immediately (cheap, reversible, prevents accidents). Schedule the actual upgrade as its own ticket with the inventory as its first deliverable. If the package underpins a critical path, target a low-traffic deploy window and stage the rollout — the silent-failure risk on filter() argues against bundling this with feature work.
Maintenance. Post-migration, maintenance burden is roughly neutral to slightly improved: specific return types reduce defensive type-checking in our own code, and semantic method names lower the cost of onboarding. The offsetting cost is that we now track a package willing to make breaking changes to core interfaces on a major bump; we should expect similar churn at the next major and budget for it rather than treat it as surprise work.
Support. The release ships with a usable migration guide, which is more than many packages offer. However, the notes are auto-generated and give no guidance on the filter() behavioral change beyond the signature — no worked example of chained predicate usage, no warning about array-native consumers. If we hit ambiguity, upstream support is likely thin; assume we self-serve. Document our own internal migration notes so the next consumer does not re-derive them.
Scaling. The type-level changes have no runtime performance implications of note — filter() returning static instead of array is a negligible allocation difference. Scaling concerns are unchanged from v2: batch size, concurrency limits, and timeout tuning remain the levers, and none are touched here. No capacity planning impact.
Failure modes. The dominant failure mode is silent behavioral breakage on filter() in loosely typed consumer code — code that iterated the old array will not necessarily raise, it may simply behave incorrectly. Secondary failure modes: fatal errors on the renamed methods (loud and therefore low-risk), and contract violations in our own test doubles and decorators (caught only if CI runs them, which it should). A third, subtler mode is a partial upgrade where a shared internal package is bumped but a downstream consumer is not, producing type mismatches at the boundary. Mitigation for all three is the same: static analysis in CI, lockstep upgrade of shared packages, and explicit tests asserting the new return types.
Ramp-up. Low. The conceptual model is unchanged — developers still build a batch, dispatch it, and inspect results. The learning delta is three method names and the insight that filter() now returns a batch rather than an array. Estimate well under a day for an engineer familiar with the package to be productive on v3, plus the inventory and migration labor for the upgrade itself. Ramp-up cost is dominated by finding affected code, not by understanding the new API — which reinforces making static analysis the centerpiece of the migration plan.
How can I help you explore Laravel packages today?