From 41731ff37e5aa3b703457d66635e037877dfd4ed Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Thu, 11 Dec 2025 08:16:04 -0500 Subject: [PATCH 1/9] runAfterResponseCallbacks --- src/Illuminate/Http/Client/PendingRequest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index f1fd87be3f22..3d869fda508a 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -165,6 +165,13 @@ class PendingRequest */ protected $beforeSendingCallbacks; + /** + * The callbacks that should execute after the Laravel Response is built. + * + * @var \Illuminate\Support\Collection + */ + protected $afterResponseCallbacks; + /** * The stub callables that will handle requests. * @@ -268,6 +275,8 @@ public function __construct(?Factory $factory = null, $middleware = []) $pendingRequest->dispatchRequestSendingEvent(); }]); + + $this->afterResponseCallbacks = new Collection(); } /** @@ -738,6 +747,18 @@ public function beforeSending($callback) }); } + /** + * Add a new callback to execute after the response is built. + * @param callable(\Illuminate\Http\Client\Response) $callback + * @return $this + */ + public function afterResponse(callable $callback) + { + $this->$afterResponseCallbacks[] = $callback; + + return $this; + } + /** * Throw an exception if a server or client error occurs. * @@ -1008,6 +1029,7 @@ public function send(string $method, string $url, array $options = []) $this->populateResponse($response); $this->dispatchResponseReceivedEvent($response); + $this->runAfterResponseCallbacks($response); if ($response->successful()) { return; @@ -1153,6 +1175,7 @@ protected function makePromise(string $method, string $url, array $options = [], return tap($this->newResponse($message), function ($response) { $this->populateResponse($response); $this->dispatchResponseReceivedEvent($response); + $this->runAfterResponseCallbacks($response); }); }) ->otherwise(function (OutOfBoundsException|TransferException|StrayRequestException $e) { @@ -1579,6 +1602,11 @@ protected function newResponse($response) }); } + protected function runAfterResponseCallbacks(Response $response) + { + $this->afterResponseCallbacks->each(static fn (callable $callback) => $callback($response)); + } + /** * Register a stub callable that will intercept requests and be able to return stub responses. * From 81a3ddf7da42db905d149c9aa027c27d349c30b7 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:48:46 -0500 Subject: [PATCH 2/9] Update PendingRequest.php --- src/Illuminate/Http/Client/PendingRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 3d869fda508a..c2346feb2ed0 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -754,7 +754,7 @@ public function beforeSending($callback) */ public function afterResponse(callable $callback) { - $this->$afterResponseCallbacks[] = $callback; + $this->afterResponseCallbacks[] = $callback; return $this; } From 9af6bd7b76063609ee12b77d62d240e5ce071286 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 13 Dec 2025 07:13:08 -0500 Subject: [PATCH 3/9] style --- src/Illuminate/Http/Client/PendingRequest.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index c2346feb2ed0..dcf99b6a53a3 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -167,7 +167,7 @@ class PendingRequest /** * The callbacks that should execute after the Laravel Response is built. - * + * * @var \Illuminate\Support\Collection */ protected $afterResponseCallbacks; @@ -749,7 +749,8 @@ public function beforeSending($callback) /** * Add a new callback to execute after the response is built. - * @param callable(\Illuminate\Http\Client\Response) $callback + * + * @param (callable(\Illuminate\Http\Client\Response): void) $callback * @return $this */ public function afterResponse(callable $callback) @@ -1602,6 +1603,12 @@ protected function newResponse($response) }); } + /** + * Execute the "after response" callbacks. + * + * @param Response $response + * @return void + */ protected function runAfterResponseCallbacks(Response $response) { $this->afterResponseCallbacks->each(static fn (callable $callback) => $callback($response)); From e2c73ed3cc596593b4e71ea2e84f982937a16e26 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 13 Dec 2025 07:17:05 -0500 Subject: [PATCH 4/9] callable type --- src/Illuminate/Http/Client/PendingRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index dcf99b6a53a3..21d67c49cf59 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -168,7 +168,7 @@ class PendingRequest /** * The callbacks that should execute after the Laravel Response is built. * - * @var \Illuminate\Support\Collection + * @var \Illuminate\Support\Collection */ protected $afterResponseCallbacks; From ce450e5b39701932acacfc6404374bc036185ae4 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 13 Dec 2025 07:22:41 -0500 Subject: [PATCH 5/9] fix runBeforeSendingCallback types --- src/Illuminate/Http/Client/PendingRequest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 21d67c49cf59..fffa2cac1065 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -1544,9 +1544,9 @@ protected function sinkStubHandler($sink) /** * Execute the "before sending" callbacks. * - * @param \GuzzleHttp\Psr7\RequestInterface $request + * @param \Psr\Http\Message\RequestInterface $request * @param array $options - * @return \GuzzleHttp\Psr7\RequestInterface + * @return \Psr\Http\Message\RequestInterface */ public function runBeforeSendingCallbacks($request, array $options) { From 3d3f0bba32ac438a715e1c581f01f5571e259b67 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 13 Dec 2025 07:55:41 -0500 Subject: [PATCH 6/9] can mutate response --- src/Illuminate/Http/Client/PendingRequest.php | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index fffa2cac1065..1f6f42f1256c 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -168,7 +168,7 @@ class PendingRequest /** * The callbacks that should execute after the Laravel Response is built. * - * @var \Illuminate\Support\Collection + * @var \Illuminate\Support\Collection */ protected $afterResponseCallbacks; @@ -750,7 +750,7 @@ public function beforeSending($callback) /** * Add a new callback to execute after the response is built. * - * @param (callable(\Illuminate\Http\Client\Response): void) $callback + * @param (callable(\Illuminate\Http\Client\Response): \Illuminate\Http\Client\Response|null) $callback * @return $this */ public function afterResponse(callable $callback) @@ -1026,11 +1026,11 @@ public function send(string $method, string $url, array $options = []) return retry($this->tries ?? 1, function ($attempt) use ($method, $url, $options, &$shouldRetry) { try { - return tap($this->newResponse($this->sendRequest($method, $url, $options)), function ($response) use ($attempt, &$shouldRetry) { + return tap($this->newResponse($this->sendRequest($method, $url, $options)), function (&$response) use ($attempt, &$shouldRetry) { $this->populateResponse($response); $this->dispatchResponseReceivedEvent($response); - $this->runAfterResponseCallbacks($response); + $response = $this->runAfterResponseCallbacks($response); if ($response->successful()) { return; @@ -1173,11 +1173,11 @@ protected function makePromise(string $method, string $url, array $options = [], { return $this->promise = $this->sendRequest($method, $url, $options) ->then(function (MessageInterface $message) { - return tap($this->newResponse($message), function ($response) { - $this->populateResponse($response); - $this->dispatchResponseReceivedEvent($response); - $this->runAfterResponseCallbacks($response); - }); + $response = $this->newResponse($message); + $this->populateResponse($response); + $this->dispatchResponseReceivedEvent($response); + + return $this->runAfterResponseCallbacks($response); }) ->otherwise(function (OutOfBoundsException|TransferException|StrayRequestException $e) { if ($e instanceof StrayRequestException) { @@ -1606,12 +1606,19 @@ protected function newResponse($response) /** * Execute the "after response" callbacks. * - * @param Response $response - * @return void + * @param \Illuminate\Http\Client\Response $response + * @return \Illuminate\Http\Client\Response */ protected function runAfterResponseCallbacks(Response $response) { - $this->afterResponseCallbacks->each(static fn (callable $callback) => $callback($response)); + foreach ($this->afterResponseCallbacks as $callback) { + $returnedResponse = $callback($response); + if ($returnedResponse instanceof Response) { + $response = $returnedResponse; + } + } + + return $response; } /** From babe5c9f1e44f971fdad1efd974d11d465abf4e9 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 13 Dec 2025 08:10:08 -0500 Subject: [PATCH 7/9] tests --- tests/Http/HttpClientTest.php | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index cd6cf6313b44..7f6ff12b4f4b 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -3567,6 +3567,7 @@ public function testItCanAddAuthorizationHeaderIntoRequestUsingBeforeSendingCall { $this->factory->fake(); + // $this->factory->beforeSending(function (Request $request) { $requestLine = sprintf( '%s %s HTTP/%s', @@ -4194,6 +4195,60 @@ public static function methodsReceivingArrayableDataProvider() 'delete' => ['delete'], ]; } + + public function testAfterResponse() + { + $this->factory->fake([ + 'http://200.com*' => $this->factory::response('OK'), + ]); + + $response = $this->factory + ->afterResponse(fn (Response $response): TestResponse => new TestResponse($response->toPsrResponse())) + ->afterResponse(fn () => 'abc') + ->afterResponse(function ($r) { + $this->assertInstanceOf(TestResponse::class, $r); + }) + ->afterResponse(fn (Response $r) => new Response($r->toPsrResponse()->withBody(Utils::streamFor(strtolower($r->body()))))) + ->get('http://200.com'); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame('ok', $response->body()); + } + + public function testAfterResponseWithThrows() + { + $this->factory->fake([ + 'http://500.com*' => $this->factory::response('oh no', 500), + ]); + + try { + $this->factory->throw() + ->afterResponse(fn ($response) => new TestResponse($response->toPsrResponse())) + ->post('http://500.com'); + } catch (RequestException $e) { + $this->assertInstanceOf(TestResponse::class, $e->response); + } + } + + public function testAfterResponseWithAsync() + { + $this->factory->fake([ + 'http://200.com*' => $this->factory::response('OK', 200), + 'http://401.com*' => $this->factory::response('Unauthorized.', 401), + ]); + + $o = $this->factory->pool(function (Pool $pool): void { + $pool->as('200')->afterResponse(fn (Response $response) => new TestResponse($response->toPsrResponse()))->get('http://200.com'); + $pool->as('401-throwing')->throw()->afterResponse(fn (Response $response) => new TestResponse($response->toPsrResponse()))->get('http://401.com'); + $pool->as('401-response')->afterResponse(fn (Response $response) => new TestResponse($response->toPsrResponse()->withBody(Utils::streamFor('different'))))->get('http://401.com'); + }, 0); + + $this->assertInstanceOf(TestResponse::class, $o['200']); + $this->assertInstanceOf(TestResponse::class, $o['401-response']); + $this->assertEquals('different', $o['401-response']->body()); + $this->assertInstanceOf(RequestException::class, $o['401-throwing']); + $this->assertInstanceOf(TestResponse::class, $o['401-throwing']->response); + } } class CustomFactory extends Factory From 072311a078d8a6afb1dcd18e5f9dc26bf73bc48c Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 13 Dec 2025 08:34:20 -0500 Subject: [PATCH 8/9] remove comment --- tests/Http/HttpClientTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 7f6ff12b4f4b..0ac82ae526b8 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -3567,7 +3567,6 @@ public function testItCanAddAuthorizationHeaderIntoRequestUsingBeforeSendingCall { $this->factory->fake(); - // $this->factory->beforeSending(function (Request $request) { $requestLine = sprintf( '%s %s HTTP/%s', From 3ee954c8f625e80867b3384c24a946e906ccc96a Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 17 Dec 2025 09:43:43 -0600 Subject: [PATCH 9/9] formatting --- src/Illuminate/Http/Client/PendingRequest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 1f6f42f1256c..e75992360d6c 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -1174,6 +1174,7 @@ protected function makePromise(string $method, string $url, array $options = [], return $this->promise = $this->sendRequest($method, $url, $options) ->then(function (MessageInterface $message) { $response = $this->newResponse($message); + $this->populateResponse($response); $this->dispatchResponseReceivedEvent($response); @@ -1613,6 +1614,7 @@ protected function runAfterResponseCallbacks(Response $response) { foreach ($this->afterResponseCallbacks as $callback) { $returnedResponse = $callback($response); + if ($returnedResponse instanceof Response) { $response = $returnedResponse; }