feat: Expose the Seam middleware for a caller built client - #472
Merged
Conversation
A client passed with the client option is used exactly as given, which means it does not carry the SDK's error mapping or retries: an API error raises Guzzle's exception rather than HttpApiError, and a retryable failure is not retried. Nothing said so, and the README's own from_client example is subject to it. Leave that behavior alone and make it opt in instead. Guzzle fixes the handler stack when a client is constructed, so the middleware cannot be added afterwards; ClientFactory::add_middleware puts it on a stack the caller builds the client with. ClientFactory::create now uses the same method, so there is one definition of the order the middleware goes on in. Applying it twice would stack two sets of retries, so it is documented as once per stack rather than guarded, since the caller owns the stack. Also correct the claim that $seam->client is the Guzzle client. It is a SerializingClient implementing ClientInterface, so Guzzle specific calls on it fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HH3wdHh4Y6Wjyc5uHwk5iG
razor-x
force-pushed
the
claude/php-audit-uoa7nb-h5-from-client-middleware
branch
from
August 19, 2026 21:32
f1f7e42 to
7baefe9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reworked per review, and rebased onto
betaata2cd8ce.Previously this PR made
from_clientinstall the SDK's middleware on an injected client. That was the wrong pattern: a passed client should be left as given, and nothing stopped the middleware being applied twice —from_client($seam->client)or a stack already carryingseam_retrywould have ended up with two sets of retries.Now the behaviour is unchanged and opt-in, with the middleware made public.
The gap being closed
A client passed with the
clientoption is used exactly as given, so it does not carry the SDK's error mapping or retries. An API error raises Guzzle's exception rather thanSeam\HttpApiError, and a retryable failure is not retried. Nothing said so, and the README's ownfrom_clientexample is subject to it.The fix
Seam\Http\ClientFactory::add_middlewareputs the error mapping and retry middleware on a handler stack the caller builds their client with:Guzzle fixes the handler stack at construction, so the middleware genuinely cannot be added afterwards — that is why this is a stack helper rather than something that takes a client.
ClientFactory::createnow calls the same method, so there is a single definition of the order the middleware goes on in (error unshifted to the outside, retry pushed).retriesis accepted and defaults toDEFAULT_RETRIES.Applying it twice stacks two sets of retries. Since the caller owns the stack, that is documented as once-per-stack rather than guarded — happy to add a guard keyed on the
seam_error/seam_retrynames if you'd rather it be enforced.Docs
New Advanced Usage section, "Adding the Seam middleware to your own client". "Overriding the client" now states plainly that the client is used as given and does not gain error mapping or retries.
Also corrected the standing claim that
$seam->client"is the Guzzle client" — it is aSeam\Http\SerializingClientimplementingClientInterface, soinstanceof Clientchecks and Guzzle-specific methods on it fail. Fixed in the README and both class docblocks.Tests
Four in
tests/ClientTest.php, all using a freshly builtGuzzleHttp\Clientrather than one taken from another Seam instance — every pre-existing test passed$seam->client, which is why the gap was invisible:testAnInjectedClientIsUsedAsGiven— sends fine, and a 404 raises Guzzle'sClientException, pinning the documented behaviour.testAddMiddlewareGivesAnInjectedClientSeamErrors— with the middleware, that 404 becomesHttpApiError.testAddMiddlewareGivesAnInjectedClientRetries— 3 attempts on a 503.testAddMiddlewareHonoursARetryCount—retries: 0gives 1 attempt.Verified the three opt-in tests genuinely depend on the helper: with
add_middleware's body disabled, all three fail (TypeError/HttpApiErrornot raised, attempt counts wrong).Full suite: 232 tests green, psalm clean,
npm run lintclean. The deprecations in the run are the resource-constructor issue from #473, not yet onbeta.Generated by Claude Code