From abadd521e358bcd504ad011d0e68f50b46d8ee93 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:05:10 -0500 Subject: [PATCH 01/24] indicate what concurrency does --- 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 dcab5cb387c1..c61d683fbaae 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -886,7 +886,7 @@ public function delete(string $url, $data = []) * Send a pool of asynchronous requests concurrently. * * @param (callable(\Illuminate\Http\Client\Pool): mixed) $callback - * @param int|null $concurrency + * @param int|null $concurrency Use null to execute the requests sequentially * @return array */ public function pool(callable $callback, ?int $concurrency = null) From e1101b105c05929280bd825dec97147f500f3503 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:21:17 -0500 Subject: [PATCH 02/24] 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 c61d683fbaae..bb891042c552 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -886,7 +886,7 @@ public function delete(string $url, $data = []) * Send a pool of asynchronous requests concurrently. * * @param (callable(\Illuminate\Http\Client\Pool): mixed) $callback - * @param int|null $concurrency Use null to execute the requests sequentially + * @param non-negative-int|null $concurrency Use null to execute the requests sequentially, 0 to execute all requests concurrently, or another positive number to limit the number of concurrent requests * @return array */ public function pool(callable $callback, ?int $concurrency = null) From 7679976153cf590283ae1db953c13b31188ccf5e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sat, 29 Nov 2025 15:40:41 -0600 Subject: [PATCH 03/24] Change default concurrency to 0 in pool method Updated the default value of the concurrency parameter in the pool method to 0 for concurrent execution. --- 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 bb891042c552..d617e8c2ef6c 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -886,10 +886,10 @@ public function delete(string $url, $data = []) * Send a pool of asynchronous requests concurrently. * * @param (callable(\Illuminate\Http\Client\Pool): mixed) $callback - * @param non-negative-int|null $concurrency Use null to execute the requests sequentially, 0 to execute all requests concurrently, or another positive number to limit the number of concurrent requests + * @param non-negative-int|null $concurrency * @return array */ - public function pool(callable $callback, ?int $concurrency = null) + public function pool(callable $callback, ?int $concurrency = 0) { $results = []; From 28b52e647873c391120a0ec128f8eb05644e2a32 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:43:54 -0500 Subject: [PATCH 04/24] 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 d617e8c2ef6c..3fe3baf81911 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -957,7 +957,7 @@ public function send(string $method, string $url, array $options = []) [$this->pendingBody, $this->pendingFiles] = [null, []]; if ($this->async) { - return $this->makePromise($method, $url, $options); + return $this->promise = new FluentPromise(fn () => $this->makePromise($method, $url, $options)); } $shouldRetry = null; From ba374c0cc885ab9937118b8536b4e243bce33374 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:45:04 -0500 Subject: [PATCH 05/24] Update FluentPromise.php --- src/Illuminate/Http/Client/FluentPromise.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/FluentPromise.php b/src/Illuminate/Http/Client/FluentPromise.php index 5ee296273936..ea63951ad1d8 100644 --- a/src/Illuminate/Http/Client/FluentPromise.php +++ b/src/Illuminate/Http/Client/FluentPromise.php @@ -2,6 +2,7 @@ namespace Illuminate\Http\Client; +use Closure; use GuzzleHttp\Promise\PromiseInterface; use Illuminate\Support\Traits\ForwardsCalls; @@ -15,9 +16,9 @@ class FluentPromise implements PromiseInterface /** * Create a new fluent promise instance. * - * @param \GuzzleHttp\Promise\PromiseInterface $guzzlePromise + * @param \GuzzleHttp\Promise\PromiseInterface|\Closure $guzzlePromise */ - public function __construct(protected PromiseInterface $guzzlePromise) + public function __construct(protected PromiseInterface|Closure $guzzlePromise) { } @@ -82,6 +83,10 @@ public function getGuzzlePromise(): PromiseInterface */ public function __call($method, $parameters) { + if (is_callable($this->guzzlePromise)) { + $this->guzzlePromise = call_user_func($this->guzzlePromise); + } + $result = $this->forwardCallTo($this->guzzlePromise, $method, $parameters); if (! $result instanceof PromiseInterface) { From 7305fa565fdc86d224ca2b3920ed50ca03e33194 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 07:39:35 -0500 Subject: [PATCH 06/24] types and pending --- src/Illuminate/Http/Client/FluentPromise.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Http/Client/FluentPromise.php b/src/Illuminate/Http/Client/FluentPromise.php index ea63951ad1d8..3fc699b41ab6 100644 --- a/src/Illuminate/Http/Client/FluentPromise.php +++ b/src/Illuminate/Http/Client/FluentPromise.php @@ -16,7 +16,7 @@ class FluentPromise implements PromiseInterface /** * Create a new fluent promise instance. * - * @param \GuzzleHttp\Promise\PromiseInterface|\Closure $guzzlePromise + * @param \GuzzleHttp\Promise\PromiseInterface|(\Closure(): \GuzzleHttp\Promise\PromiseInterface) $guzzlePromise */ public function __construct(protected PromiseInterface|Closure $guzzlePromise) { @@ -37,19 +37,19 @@ public function otherwise(callable $onRejected): PromiseInterface #[\Override] public function resolve($value): void { - $this->guzzlePromise->resolve($value); + $this->__call('resolve', [$value]); } #[\Override] public function reject($reason): void { - $this->guzzlePromise->reject($reason); + $this->__call('reject', [$reason]); } #[\Override] public function cancel(): void { - $this->guzzlePromise->cancel(); + $this->__call('cancel', []); } #[\Override] @@ -61,15 +61,19 @@ public function wait(bool $unwrap = true) #[\Override] public function getState(): string { + if (! $this->guzzlePromise instanceof PromiseInterface) { + return PromiseInterface::PENDING; + } + return $this->guzzlePromise->getState(); } /** * Get the underlying Guzzle promise. * - * @return \GuzzleHttp\Promise\PromiseInterface + * @return \GuzzleHttp\Promise\PromiseInterface|(\Closure(): \GuzzleHttp\Promise\PromiseInterface) */ - public function getGuzzlePromise(): PromiseInterface + public function getGuzzlePromise(): PromiseInterface|Closure { return $this->guzzlePromise; } From 8b824d4ca7adadea467f487f85d08161bcce1732 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 08:35:52 -0500 Subject: [PATCH 07/24] laziness and the benefits therein --- src/Illuminate/Http/Client/PendingRequest.php | 41 +++--- .../Client/{ => Promises}/FluentPromise.php | 48 ++++++- .../Http/Client/Promises/LazyPromise.php | 124 ++++++++++++++++++ 3 files changed, 194 insertions(+), 19 deletions(-) rename src/Illuminate/Http/Client/{ => Promises}/FluentPromise.php (65%) create mode 100644 src/Illuminate/Http/Client/Promises/LazyPromise.php diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 3fe3baf81911..8071a8d74020 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -18,6 +18,8 @@ use Illuminate\Http\Client\Events\ConnectionFailed; use Illuminate\Http\Client\Events\RequestSending; use Illuminate\Http\Client\Events\ResponseReceived; +use Illuminate\Http\Client\Promises\FluentPromise; +use Illuminate\Http\Client\Promises\LazyPromise; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; @@ -903,21 +905,26 @@ public function pool(callable $callback, ?int $concurrency = 0) return $results; } - $promises = []; + $concurrency = $concurrency === 0 ? count($requests) : $concurrency; - foreach ($requests as $key => $item) { - $promises[$key] = $item instanceof static ? $item->getPromise() : $item; - } + (new Collection($requests))->chunk($concurrency) + ->each(static function (Collection $requests) use ($concurrency, &$results) { + $promises = []; + foreach ($requests as $key => $item) { + $promise = $item instanceof static ? $item->getPromise() : $item; + $promise[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; + } - (new EachPromise($promises, [ - 'fulfilled' => function ($result, $key) use (&$results) { - $results[$key] = $result; - }, - 'rejected' => function ($reason, $key) use (&$results) { - $results[$key] = $reason; - }, - 'concurrency' => $concurrency, - ]))->promise()->wait(); + (new EachPromise($promises, [ + 'fulfilled' => function ($result, $key) use (&$results) { + $results[$key] = $result; + }, + 'rejected' => function ($reason, $key) use (&$results) { + $results[$key] = $reason; + }, + 'concurrency' => $concurrency, + ]))->promise()->wait(); + }); return $results; } @@ -939,7 +946,7 @@ public function batch(callable $callback): Batch * @param string $method * @param string $url * @param array $options - * @return \Illuminate\Http\Client\Response + * @return \Illuminate\Http\Client\Response|\Illuminate\Http\Client\Promises\LazyPromise * * @throws \Exception * @throws \Illuminate\Http\Client\ConnectionException @@ -957,7 +964,9 @@ public function send(string $method, string $url, array $options = []) [$this->pendingBody, $this->pendingFiles] = [null, []]; if ($this->async) { - return $this->promise = new FluentPromise(fn () => $this->makePromise($method, $url, $options)); + return $this->promise = new LazyPromise( + fn () => $this->makePromise($method, $url, $options) + ); } $shouldRetry = null; @@ -1198,7 +1207,7 @@ protected function handlePromiseResponse(Response|ConnectionException|TransferEx * @param string $method * @param string $url * @param array $options - * @return \Psr\Http\Message\MessageInterface|\Illuminate\Http\Client\FluentPromise + * @return \Psr\Http\Message\MessageInterface|\Illuminate\Http\Client\Promises\FluentPromise * * @throws \Exception */ diff --git a/src/Illuminate/Http/Client/FluentPromise.php b/src/Illuminate/Http/Client/Promises/FluentPromise.php similarity index 65% rename from src/Illuminate/Http/Client/FluentPromise.php rename to src/Illuminate/Http/Client/Promises/FluentPromise.php index 3fc699b41ab6..9e13d49b91d3 100644 --- a/src/Illuminate/Http/Client/FluentPromise.php +++ b/src/Illuminate/Http/Client/Promises/FluentPromise.php @@ -1,6 +1,6 @@ + */ + protected array $pendingThens = []; + + /** + * @var list + */ + protected array $pendingOtherwises = []; + /** * Create a new fluent promise instance. * @@ -25,12 +35,24 @@ public function __construct(protected PromiseInterface|Closure $guzzlePromise) #[\Override] public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { + if ($this->isLazy()) { + $this->pendingThens[] = [$onFulfilled, $onRejected]; + + return $this; + } + return $this->__call('then', [$onFulfilled, $onRejected]); } #[\Override] public function otherwise(callable $onRejected): PromiseInterface { + if ($this->isLazy()) { + $this->pendingOtherwises[] = $onRejected; + + return $this; + } + return $this->__call('otherwise', [$onRejected]); } @@ -68,6 +90,11 @@ public function getState(): string return $this->guzzlePromise->getState(); } + public function isLazy(): bool + { + return is_callable($this->guzzlePromise); + } + /** * Get the underlying Guzzle promise. * @@ -78,6 +105,21 @@ public function getGuzzlePromise(): PromiseInterface|Closure return $this->guzzlePromise; } + protected function convertLazyPromiseToPromise(): void + { + $this->guzzlePromise = call_user_func($this->guzzlePromise); + + if ($this->pendingThens !== []) { + array_map(fn (array $pendingThen) => $this->guzzlePromise->then(...$pendingThen), $this->pendingThens); + $this->pendingThens = []; + } + + if ($this->pendingOtherwises !== []) { + array_map(fn (callable $pendingOtherwise) => $this->guzzlePromise->otherwise($pendingOtherwise), $this->pendingOtherwises); + $this->pendingOtherwises = []; + } + } + /** * Proxy requests to the underlying promise interface and update the local promise. * @@ -87,8 +129,8 @@ public function getGuzzlePromise(): PromiseInterface|Closure */ public function __call($method, $parameters) { - if (is_callable($this->guzzlePromise)) { - $this->guzzlePromise = call_user_func($this->guzzlePromise); + if ($this->isLazy()) { + $this->convertLazyPromiseToPromise(); } $result = $this->forwardCallTo($this->guzzlePromise, $method, $parameters); diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php new file mode 100644 index 000000000000..1bf69359efec --- /dev/null +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -0,0 +1,124 @@ + + */ + protected array $pendingThens = []; + + /** + * @var list + */ + protected array $pendingOtherwises = []; + + protected PromiseInterface $guzzlePromise; + + /** + * Create a new fluent promise instance. + * + * @param \Closure(): \GuzzleHttp\Promise\PromiseInterface $promiseBuilder + */ + public function __construct(protected Closure $promiseBuilder) + { + } + + public function buildPromise(): PromiseInterface + { + if (isset($this->guzzlePromise)) { + throw new \RuntimeException('Promise already built'); + } + + $this->guzzlePromise = call_user_func($this->promiseBuilder); + + if ($this->pendingThens !== []) { + array_map(fn (array $pendingThen) => $this->guzzlePromise->then(...$pendingThen), $this->pendingThens); + $this->pendingThens = []; + } + + if ($this->pendingOtherwises !== []) { + array_map(fn (callable $pendingOtherwise) => $this->guzzlePromise->otherwise($pendingOtherwise), $this->pendingOtherwises); + $this->pendingOtherwises = []; + } + + return $this->guzzlePromise; + } + + public function isLazy(): bool + { + return ! isset($this->guzzlePromise); + } + + #[\Override] + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface + { + $this->pendingThens[] = [$onFulfilled, $onRejected]; + + return $this; + } + + #[\Override] + public function otherwise(callable $onRejected): PromiseInterface + { + $this->pendingOtherwises[] = $onRejected; + + return $this; + } + + /** + * @inheritDoc + */ + #[\Override] + public function getState(): string + { + return PromiseInterface::PENDING; + } + + /** + * @inheritDoc + */ + #[\Override] + public function resolve($value): void + { + throw new \LogicException('Cannot resolve a lazy promise.'); + } + + /** + * @inheritDoc + */ + #[\Override] + public function reject($reason): void + { + throw new \LogicException('Cannot reject a lazy promise.'); + } + + /** + * @inheritDoc + */ + #[\Override] + public function cancel(): void + { + throw new \LogicException('Cannot cancel a lazy promise.'); + } + + /** + * @inheritDoc + */ + #[\Override] + public function wait(bool $unwrap = true) + { + if ($this->isLazy()) { + $this->buildPromise(); + } + + return $this->guzzlePromise->wait($unwrap); + } +} From 12fdfeb5b2baa59da31718d22191def6f5463c01 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 08:45:07 -0500 Subject: [PATCH 08/24] wip --- .../Http/Client/Promises/FluentPromise.php | 65 ++----------------- .../Http/Client/Promises/LazyPromise.php | 50 ++++++++------ 2 files changed, 37 insertions(+), 78 deletions(-) diff --git a/src/Illuminate/Http/Client/Promises/FluentPromise.php b/src/Illuminate/Http/Client/Promises/FluentPromise.php index 9e13d49b91d3..1db30e408f27 100644 --- a/src/Illuminate/Http/Client/Promises/FluentPromise.php +++ b/src/Illuminate/Http/Client/Promises/FluentPromise.php @@ -2,7 +2,6 @@ namespace Illuminate\Http\Client\Promises; -use Closure; use GuzzleHttp\Promise\PromiseInterface; use Illuminate\Support\Traits\ForwardsCalls; @@ -13,65 +12,43 @@ class FluentPromise implements PromiseInterface { use ForwardsCalls; - /** - * @var list - */ - protected array $pendingThens = []; - - /** - * @var list - */ - protected array $pendingOtherwises = []; - /** * Create a new fluent promise instance. * - * @param \GuzzleHttp\Promise\PromiseInterface|(\Closure(): \GuzzleHttp\Promise\PromiseInterface) $guzzlePromise + * @param \GuzzleHttp\Promise\PromiseInterface $guzzlePromise */ - public function __construct(protected PromiseInterface|Closure $guzzlePromise) + public function __construct(protected PromiseInterface $guzzlePromise) { } #[\Override] public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { - if ($this->isLazy()) { - $this->pendingThens[] = [$onFulfilled, $onRejected]; - - return $this; - } - return $this->__call('then', [$onFulfilled, $onRejected]); } #[\Override] public function otherwise(callable $onRejected): PromiseInterface { - if ($this->isLazy()) { - $this->pendingOtherwises[] = $onRejected; - - return $this; - } - return $this->__call('otherwise', [$onRejected]); } #[\Override] public function resolve($value): void { - $this->__call('resolve', [$value]); + $this->guzzlePromise->resolve($value); } #[\Override] public function reject($reason): void { - $this->__call('reject', [$reason]); + $this->guzzlePromise->reject($reason); } #[\Override] public function cancel(): void { - $this->__call('cancel', []); + $this->guzzlePromise->cancel(); } #[\Override] @@ -83,43 +60,19 @@ public function wait(bool $unwrap = true) #[\Override] public function getState(): string { - if (! $this->guzzlePromise instanceof PromiseInterface) { - return PromiseInterface::PENDING; - } - return $this->guzzlePromise->getState(); } - public function isLazy(): bool - { - return is_callable($this->guzzlePromise); - } - /** * Get the underlying Guzzle promise. * - * @return \GuzzleHttp\Promise\PromiseInterface|(\Closure(): \GuzzleHttp\Promise\PromiseInterface) + * @return \GuzzleHttp\Promise\PromiseInterface */ - public function getGuzzlePromise(): PromiseInterface|Closure + public function getGuzzlePromise(): PromiseInterface { return $this->guzzlePromise; } - protected function convertLazyPromiseToPromise(): void - { - $this->guzzlePromise = call_user_func($this->guzzlePromise); - - if ($this->pendingThens !== []) { - array_map(fn (array $pendingThen) => $this->guzzlePromise->then(...$pendingThen), $this->pendingThens); - $this->pendingThens = []; - } - - if ($this->pendingOtherwises !== []) { - array_map(fn (callable $pendingOtherwise) => $this->guzzlePromise->otherwise($pendingOtherwise), $this->pendingOtherwises); - $this->pendingOtherwises = []; - } - } - /** * Proxy requests to the underlying promise interface and update the local promise. * @@ -129,10 +82,6 @@ protected function convertLazyPromiseToPromise(): void */ public function __call($method, $parameters) { - if ($this->isLazy()) { - $this->convertLazyPromiseToPromise(); - } - $result = $this->forwardCallTo($this->guzzlePromise, $method, $parameters); if (! $result instanceof PromiseInterface) { diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index 1bf69359efec..22ae8a187cb6 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -5,6 +5,7 @@ use Closure; use GuzzleHttp\Promise\PromiseInterface; use Illuminate\Support\Traits\ForwardsCalls; +use RuntimeException; class LazyPromise implements PromiseInterface { @@ -20,6 +21,11 @@ class LazyPromise implements PromiseInterface */ protected array $pendingOtherwises = []; + /** + * @var list}> + */ + protected array $pending = []; + protected PromiseInterface $guzzlePromise; /** @@ -31,14 +37,22 @@ public function __construct(protected Closure $promiseBuilder) { } + /** + * Build the promise from the lazy promise builder. + * + * @return PromiseInterface + * + * @throws \RuntimeException If the promise has already been built + */ public function buildPromise(): PromiseInterface { if (isset($this->guzzlePromise)) { - throw new \RuntimeException('Promise already built'); + throw new RuntimeException('Promise already built'); } $this->guzzlePromise = call_user_func($this->promiseBuilder); + if ($this->pendingThens !== []) { array_map(fn (array $pendingThen) => $this->guzzlePromise->then(...$pendingThen), $this->pendingThens); $this->pendingThens = []; @@ -52,7 +66,12 @@ public function buildPromise(): PromiseInterface return $this->guzzlePromise; } - public function isLazy(): bool + /** + * If the promise has been created from the promise builder. + * + * @return bool + */ + public function promiseNeedsBuilt(): bool { return ! isset($this->guzzlePromise); } @@ -60,7 +79,10 @@ public function isLazy(): bool #[\Override] public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { - $this->pendingThens[] = [$onFulfilled, $onRejected]; + $this->pending[] = [ + 'method' => 'then', + 'params' => [$onFulfilled, $onRejected], + ]; return $this; } @@ -68,54 +90,42 @@ public function then(?callable $onFulfilled = null, ?callable $onRejected = null #[\Override] public function otherwise(callable $onRejected): PromiseInterface { - $this->pendingOtherwises[] = $onRejected; + $this->pending[] = [ + 'method' => 'otherwise', + 'params' => [$onRejected], + ]; return $this; } - /** - * @inheritDoc - */ #[\Override] public function getState(): string { return PromiseInterface::PENDING; } - /** - * @inheritDoc - */ #[\Override] public function resolve($value): void { throw new \LogicException('Cannot resolve a lazy promise.'); } - /** - * @inheritDoc - */ #[\Override] public function reject($reason): void { throw new \LogicException('Cannot reject a lazy promise.'); } - /** - * @inheritDoc - */ #[\Override] public function cancel(): void { throw new \LogicException('Cannot cancel a lazy promise.'); } - /** - * @inheritDoc - */ #[\Override] public function wait(bool $unwrap = true) { - if ($this->isLazy()) { + if ($this->promiseNeedsBuilt()) { $this->buildPromise(); } From a8105c8918e5eb674f58949e35552bde3b57256f Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 09:26:46 -0500 Subject: [PATCH 09/24] FluentPromise is dead, long live LazyPromise --- src/Illuminate/Http/Client/PendingRequest.php | 12 +-- .../Http/Client/Promises/FluentPromise.php | 95 ------------------- .../Http/Client/Promises/LazyPromise.php | 35 ++----- 3 files changed, 9 insertions(+), 133 deletions(-) delete mode 100644 src/Illuminate/Http/Client/Promises/FluentPromise.php diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 8071a8d74020..e5b27886eb86 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -12,13 +12,11 @@ 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; use Illuminate\Http\Client\Events\RequestSending; use Illuminate\Http\Client\Events\ResponseReceived; -use Illuminate\Http\Client\Promises\FluentPromise; use Illuminate\Http\Client\Promises\LazyPromise; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -1207,7 +1205,7 @@ protected function handlePromiseResponse(Response|ConnectionException|TransferEx * @param string $method * @param string $url * @param array $options - * @return \Psr\Http\Message\MessageInterface|\Illuminate\Http\Client\Promises\FluentPromise + * @return \Psr\Http\Message\MessageInterface|\GuzzleHttp\Promise\PromiseInterface * * @throws \Exception */ @@ -1230,13 +1228,7 @@ protected function sendRequest(string $method, string $url, array $options = []) 'on_stats' => $onStats, ], $options)); - $result = $this->buildClient()->$clientMethod($method, $url, $mergedOptions); - - if ($result instanceof PromiseInterface && ! $result instanceof FluentPromise) { - $result = new FluentPromise($result); - } - - return $result; + return $this->buildClient()->$clientMethod($method, $url, $mergedOptions); } /** diff --git a/src/Illuminate/Http/Client/Promises/FluentPromise.php b/src/Illuminate/Http/Client/Promises/FluentPromise.php deleted file mode 100644 index 1db30e408f27..000000000000 --- a/src/Illuminate/Http/Client/Promises/FluentPromise.php +++ /dev/null @@ -1,95 +0,0 @@ -__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/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index 22ae8a187cb6..5911df56220b 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -12,18 +12,10 @@ class LazyPromise implements PromiseInterface use ForwardsCalls; /** - * @var list - */ - protected array $pendingThens = []; - - /** + * The callbacks to execute after the Promise has been built. + * * @var list */ - protected array $pendingOtherwises = []; - - /** - * @var list}> - */ protected array $pending = []; protected PromiseInterface $guzzlePromise; @@ -31,7 +23,7 @@ class LazyPromise implements PromiseInterface /** * Create a new fluent promise instance. * - * @param \Closure(): \GuzzleHttp\Promise\PromiseInterface $promiseBuilder + * @param (\Closure(): \GuzzleHttp\Promise\PromiseInterface) $promiseBuilder */ public function __construct(protected Closure $promiseBuilder) { @@ -52,15 +44,8 @@ public function buildPromise(): PromiseInterface $this->guzzlePromise = call_user_func($this->promiseBuilder); - - if ($this->pendingThens !== []) { - array_map(fn (array $pendingThen) => $this->guzzlePromise->then(...$pendingThen), $this->pendingThens); - $this->pendingThens = []; - } - - if ($this->pendingOtherwises !== []) { - array_map(fn (callable $pendingOtherwise) => $this->guzzlePromise->otherwise($pendingOtherwise), $this->pendingOtherwises); - $this->pendingOtherwises = []; + foreach($this->pending as $pendingCallback) { + $pendingCallback($this->guzzlePromise); } return $this->guzzlePromise; @@ -79,10 +64,7 @@ public function promiseNeedsBuilt(): bool #[\Override] public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { - $this->pending[] = [ - 'method' => 'then', - 'params' => [$onFulfilled, $onRejected], - ]; + $this->pending[] = static fn (PromiseInterface $promise) => $promise->then($onFulfilled, $onRejected); return $this; } @@ -90,10 +72,7 @@ public function then(?callable $onFulfilled = null, ?callable $onRejected = null #[\Override] public function otherwise(callable $onRejected): PromiseInterface { - $this->pending[] = [ - 'method' => 'otherwise', - 'params' => [$onRejected], - ]; + $this->pending[] = static fn (PromiseInterface $promise) => $promise->otherwise($onRejected); return $this; } From 5911d2dc00153bce9963fee5c133c3c4e7d70feb Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 09:33:55 -0500 Subject: [PATCH 10/24] clean up --- src/Illuminate/Http/Client/PendingRequest.php | 2 +- src/Illuminate/Http/Client/Promises/LazyPromise.php | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index e5b27886eb86..8192febc64bb 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -910,7 +910,7 @@ public function pool(callable $callback, ?int $concurrency = 0) $promises = []; foreach ($requests as $key => $item) { $promise = $item instanceof static ? $item->getPromise() : $item; - $promise[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; + $promises[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; } (new EachPromise($promises, [ diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index 5911df56220b..b41db02f3e48 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -32,13 +32,13 @@ public function __construct(protected Closure $promiseBuilder) /** * Build the promise from the lazy promise builder. * - * @return PromiseInterface + * @return \GuzzleHttp\Promise\PromiseInterface * * @throws \RuntimeException If the promise has already been built */ public function buildPromise(): PromiseInterface { - if (isset($this->guzzlePromise)) { + if (! $this->promiseNeedsBuilt()) { throw new RuntimeException('Promise already built'); } @@ -80,7 +80,11 @@ public function otherwise(callable $onRejected): PromiseInterface #[\Override] public function getState(): string { - return PromiseInterface::PENDING; + if ($this->promiseNeedsBuilt()) { + return PromiseInterface::PENDING; + } + + return $this->guzzlePromise->getState(); } #[\Override] From 5caea0f7a7b210594043075a97294e6047b02c06 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 09:36:21 -0500 Subject: [PATCH 11/24] fix test --- src/Illuminate/Http/Client/Promises/LazyPromise.php | 4 ++-- tests/Http/HttpClientTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index b41db02f3e48..a48be9c4be99 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -12,7 +12,7 @@ class LazyPromise implements PromiseInterface use ForwardsCalls; /** - * The callbacks to execute after the Promise has been built. + * The callbacks to execute after the Guzzle Promise has been built. * * @var list */ @@ -21,7 +21,7 @@ class LazyPromise implements PromiseInterface protected PromiseInterface $guzzlePromise; /** - * Create a new fluent promise instance. + * Create a new lazy promise instance. * * @param (\Closure(): \GuzzleHttp\Promise\PromiseInterface) $promiseBuilder */ diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index febc488c3a91..2aa862a169f5 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -3534,7 +3534,7 @@ public function testItCanEnforceFakingInThePool() return [ $pool->get('https://laravel.com'), ]; - }); + }, null); } public function testPreventingStrayRequests() From 2355a99b4fcba8fbace40780645cae0538f19a21 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 09:55:02 -0500 Subject: [PATCH 12/24] actually, FluentPromise still has its place --- src/Illuminate/Http/Client/PendingRequest.php | 10 +- .../Http/Client/Promises/FluentPromise.php | 95 +++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/Illuminate/Http/Client/Promises/FluentPromise.php diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 8192febc64bb..469d18b886d7 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -12,11 +12,13 @@ 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; use Illuminate\Http\Client\Events\RequestSending; use Illuminate\Http\Client\Events\ResponseReceived; +use Illuminate\Http\Client\Promises\FluentPromise; use Illuminate\Http\Client\Promises\LazyPromise; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -1228,7 +1230,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/src/Illuminate/Http/Client/Promises/FluentPromise.php b/src/Illuminate/Http/Client/Promises/FluentPromise.php new file mode 100644 index 000000000000..1db30e408f27 --- /dev/null +++ b/src/Illuminate/Http/Client/Promises/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; + } +} From a84c5e531abbe08caaf32c7c542da8c9ea01fbdd Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 09:59:26 -0500 Subject: [PATCH 13/24] clean up LazyPromise --- src/Illuminate/Http/Client/Promises/LazyPromise.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index a48be9c4be99..783a2f34b9df 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -18,19 +18,24 @@ class LazyPromise implements PromiseInterface */ protected array $pending = []; + /** + * The promise built by the creator. + * + * @var \GuzzleHttp\Promise\PromiseInterface + */ protected PromiseInterface $guzzlePromise; /** * Create a new lazy promise instance. * - * @param (\Closure(): \GuzzleHttp\Promise\PromiseInterface) $promiseBuilder + * @param (\Closure(): \GuzzleHttp\Promise\PromiseInterface) $promiseBuilder The callback to build a new PromiseInterface. */ public function __construct(protected Closure $promiseBuilder) { } /** - * Build the promise from the lazy promise builder. + * Build the promise from the promise builder. * * @return \GuzzleHttp\Promise\PromiseInterface * @@ -44,7 +49,7 @@ public function buildPromise(): PromiseInterface $this->guzzlePromise = call_user_func($this->promiseBuilder); - foreach($this->pending as $pendingCallback) { + foreach ($this->pending as $pendingCallback) { $pendingCallback($this->guzzlePromise); } From 8e8427cf009bf6be0bbc8752475dd401496fc70d Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 10:21:55 -0500 Subject: [PATCH 14/24] backwards compatibility --- src/Illuminate/Http/Client/PendingRequest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 469d18b886d7..4c2a994d8bda 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -898,6 +898,14 @@ public function pool(callable $callback, ?int $concurrency = 0) $requests = tap(new Pool($this->factory), $callback)->getRequests(); if ($concurrency === null) { + (new Collection($requests))->each(function ($item) { + if ($item instanceof static) { + $item = $item->getPromise(); + } + if ($item instanceof LazyPromise) { + $item->buildPromise(); + } + }); foreach ($requests as $key => $item) { $results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait(); } From bea0372fbba094f99bf906bc21cacd832f3b379e Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 10:45:54 -0500 Subject: [PATCH 15/24] remove unused trait --- src/Illuminate/Http/Client/Promises/LazyPromise.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index 783a2f34b9df..627dc61fc8b2 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -4,13 +4,10 @@ use Closure; use GuzzleHttp\Promise\PromiseInterface; -use Illuminate\Support\Traits\ForwardsCalls; use RuntimeException; class LazyPromise implements PromiseInterface { - use ForwardsCalls; - /** * The callbacks to execute after the Guzzle Promise has been built. * From 4a74bc15c361b0218b48e533982c3e7f63f07d70 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 10:55:16 -0500 Subject: [PATCH 16/24] return concurrency to null --- 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 4c2a994d8bda..d874599afb1a 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -891,7 +891,7 @@ public function delete(string $url, $data = []) * @param non-negative-int|null $concurrency * @return array */ - public function pool(callable $callback, ?int $concurrency = 0) + public function pool(callable $callback, ?int $concurrency = null) { $results = []; From 79fdf659aa278fdb95f20d8886774baf5107b4ae Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 11:33:06 -0500 Subject: [PATCH 17/24] fix Batch --- src/Illuminate/Http/Client/Batch.php | 29 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Illuminate/Http/Client/Batch.php b/src/Illuminate/Http/Client/Batch.php index 5a6baf51aa31..c8993858fc56 100644 --- a/src/Illuminate/Http/Client/Batch.php +++ b/src/Illuminate/Http/Client/Batch.php @@ -7,6 +7,8 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Promise\EachPromise; use GuzzleHttp\Utils; +use Illuminate\Http\Client\Promises\LazyPromise; +use Illuminate\Support\Collection; use Illuminate\Support\Defer\DeferredCallback; use function Illuminate\Support\defer; @@ -252,18 +254,8 @@ public function send(): array } $results = []; - $promises = []; - foreach ($this->requests as $key => $item) { - $promise = match (true) { - $item instanceof PendingRequest => $item->getPromise(), - default => $item, - }; - - $promises[$key] = $promise; - } - - if (! empty($promises)) { + if (! empty($this->requests)) { $eachPromiseOptions = [ 'fulfilled' => function ($result, $key) use (&$results) { $results[$key] = $result; @@ -307,11 +299,18 @@ public function send(): array }, ]; - if ($this->concurrencyLimit !== null) { - $eachPromiseOptions['concurrency'] = $this->concurrencyLimit; - } + $concurrency = $this->concurrencyLimit ?? count($this->requests); - (new EachPromise($promises, $eachPromiseOptions))->promise()->wait(); + (new Collection($this->requests)) + ->chunk($concurrency) + ->each(function (Collection $requestsChunk) use (&$results, $eachPromiseOptions) { + $promises = []; + foreach ($requestsChunk as $key => $item) { + $promise = $item instanceof PendingRequest ? $item->getPromise() : $item; + $promises[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; + } + (new EachPromise($promises, $eachPromiseOptions))->promise()->wait(); + }); } // Before returning the results, we must ensure that the results are sorted From e1811c349a913ab6b739d4ef2cf83c90fe5b6fff Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sun, 30 Nov 2025 11:35:47 -0500 Subject: [PATCH 18/24] static analysis --- src/Illuminate/Http/Client/Batch.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/Batch.php b/src/Illuminate/Http/Client/Batch.php index c8993858fc56..f507071e4c6e 100644 --- a/src/Illuminate/Http/Client/Batch.php +++ b/src/Illuminate/Http/Client/Batch.php @@ -303,14 +303,14 @@ public function send(): array (new Collection($this->requests)) ->chunk($concurrency) - ->each(function (Collection $requestsChunk) use (&$results, $eachPromiseOptions) { + ->each(function (Collection $requestsChunk) use ($eachPromiseOptions) { $promises = []; foreach ($requestsChunk as $key => $item) { $promise = $item instanceof PendingRequest ? $item->getPromise() : $item; $promises[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; } (new EachPromise($promises, $eachPromiseOptions))->promise()->wait(); - }); + }); } // Before returning the results, we must ensure that the results are sorted From 823a70c5b78a900aae5d753fecdb42abc1b44127 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Mon, 1 Dec 2025 07:15:17 -0500 Subject: [PATCH 19/24] convert to generator --- src/Illuminate/Http/Client/PendingRequest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index d874599afb1a..e9174b399501 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -915,6 +915,23 @@ public function pool(callable $callback, ?int $concurrency = null) $concurrency = $concurrency === 0 ? count($requests) : $concurrency; + $promiseGenerator = function () use ($requests) { + foreach ($requests as $key => $item) { + $promise = $item instanceof static ? $item->getPromise() : $item; + yield $key => $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; + } + }; + + (new EachPromise($promiseGenerator(), [ + 'fulfilled' => function ($result, $key) use (&$results) { + $results[$key] = $result; + }, + 'rejected' => function ($reason, $key) use (&$results) { + $results[$key] = $reason; + }, + 'concurrency' => $concurrency, + ]))->promise()->wait(); + /* (new Collection($requests))->chunk($concurrency) ->each(static function (Collection $requests) use ($concurrency, &$results) { $promises = []; @@ -935,6 +952,7 @@ public function pool(callable $callback, ?int $concurrency = null) }); return $results; + */ } /** From fbc8d1244c50bc6c2906bce3f3c73f14a3691ef0 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Mon, 1 Dec 2025 14:41:21 -0500 Subject: [PATCH 20/24] clean up PendingRequest --- src/Illuminate/Http/Client/PendingRequest.php | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index e9174b399501..eb6dce872b32 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -931,28 +931,8 @@ public function pool(callable $callback, ?int $concurrency = null) }, 'concurrency' => $concurrency, ]))->promise()->wait(); - /* - (new Collection($requests))->chunk($concurrency) - ->each(static function (Collection $requests) use ($concurrency, &$results) { - $promises = []; - foreach ($requests as $key => $item) { - $promise = $item instanceof static ? $item->getPromise() : $item; - $promises[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; - } - - (new EachPromise($promises, [ - 'fulfilled' => function ($result, $key) use (&$results) { - $results[$key] = $result; - }, - 'rejected' => function ($reason, $key) use (&$results) { - $results[$key] = $reason; - }, - 'concurrency' => $concurrency, - ]))->promise()->wait(); - }); return $results; - */ } /** From 7f6e4b8773e9f0e979c1e1a2bc575ef182d277ee Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Mon, 1 Dec 2025 14:47:44 -0500 Subject: [PATCH 21/24] clean up Batch --- src/Illuminate/Http/Client/Batch.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Illuminate/Http/Client/Batch.php b/src/Illuminate/Http/Client/Batch.php index f507071e4c6e..5f5ceb327873 100644 --- a/src/Illuminate/Http/Client/Batch.php +++ b/src/Illuminate/Http/Client/Batch.php @@ -299,18 +299,20 @@ public function send(): array }, ]; - $concurrency = $this->concurrencyLimit ?? count($this->requests); - - (new Collection($this->requests)) - ->chunk($concurrency) - ->each(function (Collection $requestsChunk) use ($eachPromiseOptions) { - $promises = []; - foreach ($requestsChunk as $key => $item) { - $promise = $item instanceof PendingRequest ? $item->getPromise() : $item; - $promises[$key] = $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; - } - (new EachPromise($promises, $eachPromiseOptions))->promise()->wait(); - }); + if ($this->concurrencyLimit !== null) { + $eachPromiseOptions['concurrency'] = $this->concurrencyLimit; + } + + $promiseGenerator = function () { + foreach ($this->requests as $key => $item) { + $promise = $item instanceof PendingRequest ? $item->getPromise() : $item; + yield $key => $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; + } + }; + + (new EachPromise($promiseGenerator(), $eachPromiseOptions)) + ->promise() + ->wait(); } // Before returning the results, we must ensure that the results are sorted From cc5a13aec9b992d93c064edc5355c3eabd85f8ae Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:36:30 -0500 Subject: [PATCH 22/24] static all the things --- 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 eb6dce872b32..a1225d6d3c85 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -898,7 +898,7 @@ public function pool(callable $callback, ?int $concurrency = null) $requests = tap(new Pool($this->factory), $callback)->getRequests(); if ($concurrency === null) { - (new Collection($requests))->each(function ($item) { + (new Collection($requests))->each(static function ($item) { if ($item instanceof static) { $item = $item->getPromise(); } @@ -915,7 +915,7 @@ public function pool(callable $callback, ?int $concurrency = null) $concurrency = $concurrency === 0 ? count($requests) : $concurrency; - $promiseGenerator = function () use ($requests) { + $promiseGenerator = static function () use ($requests) { foreach ($requests as $key => $item) { $promise = $item instanceof static ? $item->getPromise() : $item; yield $key => $promise instanceof LazyPromise ? $promise->buildPromise() : $promise; From a6d17ee8e3307fbdeb9035b8592c8cb8aa92fa3d Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Tue, 2 Dec 2025 05:59:58 -0500 Subject: [PATCH 23/24] Update LazyPromise.php --- .../Http/Client/Promises/LazyPromise.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index 627dc61fc8b2..115310804d98 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -50,6 +50,8 @@ public function buildPromise(): PromiseInterface $pendingCallback($this->guzzlePromise); } + $this->pending = []; + return $this->guzzlePromise; } @@ -66,17 +68,25 @@ public function promiseNeedsBuilt(): bool #[\Override] public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { - $this->pending[] = static fn (PromiseInterface $promise) => $promise->then($onFulfilled, $onRejected); + if ($this->promiseNeedsBuilt()) { + $this->pending[] = static fn (PromiseInterface $promise) => $promise->then($onFulfilled, $onRejected); + + return $this; + } - return $this; + return $this->guzzlePromise->then($onFulfilled, $onRejected); } #[\Override] public function otherwise(callable $onRejected): PromiseInterface { - $this->pending[] = static fn (PromiseInterface $promise) => $promise->otherwise($onRejected); + if ($this->promiseNeedsBuilt()) { + $this->pending[] = static fn (PromiseInterface $promise) => $promise->otherwise($onRejected); + + return $this; + } - return $this; + return $this->guzzlePromise->otherwise($onRejected); } #[\Override] From fcb4e8453c6ea0a9e94788ad1e4d9fb8dde1d480 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 8 Dec 2025 16:06:43 -0600 Subject: [PATCH 24/24] formatting --- src/Illuminate/Http/Client/PendingRequest.php | 2 ++ .../Http/Client/Promises/LazyPromise.php | 20 +++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index a1225d6d3c85..a7531a790590 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -902,10 +902,12 @@ public function pool(callable $callback, ?int $concurrency = null) if ($item instanceof static) { $item = $item->getPromise(); } + if ($item instanceof LazyPromise) { $item->buildPromise(); } }); + foreach ($requests as $key => $item) { $results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait(); } diff --git a/src/Illuminate/Http/Client/Promises/LazyPromise.php b/src/Illuminate/Http/Client/Promises/LazyPromise.php index 115310804d98..a986e1376b90 100644 --- a/src/Illuminate/Http/Client/Promises/LazyPromise.php +++ b/src/Illuminate/Http/Client/Promises/LazyPromise.php @@ -55,16 +55,6 @@ public function buildPromise(): PromiseInterface return $this->guzzlePromise; } - /** - * If the promise has been created from the promise builder. - * - * @return bool - */ - public function promiseNeedsBuilt(): bool - { - return ! isset($this->guzzlePromise); - } - #[\Override] public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { @@ -126,4 +116,14 @@ public function wait(bool $unwrap = true) return $this->guzzlePromise->wait($unwrap); } + + /** + * Determine if the promise has been created from the promise builder. + * + * @return bool + */ + public function promiseNeedsBuilt(): bool + { + return ! isset($this->guzzlePromise); + } }