diff --git a/src/Illuminate/Http/Client/FluentPromise.php b/src/Illuminate/Http/Client/FluentPromise.php new file mode 100644 index 000000000000..5ee296273936 --- /dev/null +++ b/src/Illuminate/Http/Client/FluentPromise.php @@ -0,0 +1,95 @@ +__call('then', [$onFulfilled, $onRejected]); + } + + #[\Override] + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->__call('otherwise', [$onRejected]); + } + + #[\Override] + public function resolve($value): void + { + $this->guzzlePromise->resolve($value); + } + + #[\Override] + public function reject($reason): void + { + $this->guzzlePromise->reject($reason); + } + + #[\Override] + public function cancel(): void + { + $this->guzzlePromise->cancel(); + } + + #[\Override] + public function wait(bool $unwrap = true) + { + return $this->__call('wait', [$unwrap]); + } + + #[\Override] + public function getState(): string + { + return $this->guzzlePromise->getState(); + } + + /** + * Get the underlying Guzzle promise. + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function getGuzzlePromise(): PromiseInterface + { + return $this->guzzlePromise; + } + + /** + * Proxy requests to the underlying promise interface and update the local promise. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $result = $this->forwardCallTo($this->guzzlePromise, $method, $parameters); + + if (! $result instanceof PromiseInterface) { + return $result; + } + + $this->guzzlePromise = $result; + + return $this; + } +} diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index c3c7e2c39624..dcab5cb387c1 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -12,6 +12,7 @@ use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; use GuzzleHttp\Promise\EachPromise; +use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\UriTemplate\UriTemplate; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\Client\Events\ConnectionFailed; @@ -1197,7 +1198,7 @@ protected function handlePromiseResponse(Response|ConnectionException|TransferEx * @param string $method * @param string $url * @param array $options - * @return \Psr\Http\Message\MessageInterface|\GuzzleHttp\Promise\PromiseInterface + * @return \Psr\Http\Message\MessageInterface|\Illuminate\Http\Client\FluentPromise * * @throws \Exception */ @@ -1220,7 +1221,13 @@ protected function sendRequest(string $method, string $url, array $options = []) 'on_stats' => $onStats, ], $options)); - return $this->buildClient()->$clientMethod($method, $url, $mergedOptions); + $result = $this->buildClient()->$clientMethod($method, $url, $mergedOptions); + + if ($result instanceof PromiseInterface && ! $result instanceof FluentPromise) { + $result = new FluentPromise($result); + } + + return $result; } /** diff --git a/tests/Integration/Http/HttpClientTest.php b/tests/Integration/Http/HttpClientTest.php index 8e2b75dfaaf6..d4f832ce3052 100644 --- a/tests/Integration/Http/HttpClientTest.php +++ b/tests/Integration/Http/HttpClientTest.php @@ -3,6 +3,9 @@ namespace Illuminate\Tests\Integration\Http; use Illuminate\Http\Client\Events\RequestSending; +use Illuminate\Http\Client\PendingRequest; +use Illuminate\Http\Client\Pool; +use Illuminate\Http\Client\Response; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Facade; @@ -37,4 +40,52 @@ public function testGlobalMiddlewarePersistsAfterFacadeFlush(): void $this->assertCount(2, Http::getGlobalMiddleware()); } + + public function testPoolCanForwardToUnderlyingPromise() + { + Http::fake([ + 'https://laravel.com*' => Http::response('Laravel'), + 'https://forge.laravel.com*' => Http::response('Forge'), + 'https://nightwatch.laravel.com*' => Http::response('Tim n Jess'), + ]); + + $responses = Http::pool(function (Pool $pool) { + $pool->as('laravel')->get('https://laravel.com'); + + $pool->as('forge') + ->get('https://forge.laravel.com') + ->then(function (Response $response): int { + return strlen($response->getBody()); + }); + + $pool->as('nightwatch') + ->get('https://nightwatch.laravel.com') + ->then(fn (): int => 1) + ->then(fn ($i): int => $i + 199); + }, 3); + + $this->assertInstanceOf(Response::class, $responses['laravel']); + $this->assertEquals(5, $responses['forge']); + $this->assertEquals(200, $responses['nightwatch']); + + $this->assertCount(3, Http::recorded()); + } + + public function testForwardsCallsToPromise() + { + Http::fake(['*' => Http::response('faked response')]); + + $myFakedResponse = null; + $r = Http::async() + ->get('https://laravel.com') + ->then(function (Response $response) use (&$myFakedResponse): string { + $myFakedResponse = $response->getBody(); + + return 'stub'; + }) + ->wait(); + + $this->assertEquals('faked response', $myFakedResponse); + $this->assertEquals('stub', $r); + } }