diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index a3a022c352f3..cfab639cf511 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -19,6 +19,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use Illuminate\Support\Stringable; use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; use JsonSerializable; @@ -1035,10 +1036,12 @@ protected function sendRequest(string $method, string $url, array $options = []) $this->transferStats = $transferStats; }; - return $this->buildClient()->$clientMethod($method, $url, $this->mergeOptions([ + $mergedOptions = $this->normalizeRequestOptions($this->mergeOptions([ 'laravel_data' => $laravelData, 'on_stats' => $onStats, ], $options)); + + return $this->buildClient()->$clientMethod($method, $url, $mergedOptions); } /** @@ -1076,6 +1079,25 @@ protected function parseRequestData($method, $url, array $options) return is_array($laravelData) ? $laravelData : []; } + /** + * Normalize the given request options. + * + * @param array $options + * @return array + */ + protected function normalizeRequestOptions(array $options) + { + foreach ($options as $key => $value) { + $options[$key] = match (true) { + is_array($value) => $this->normalizeRequestOptions($value), + $value instanceof Stringable => $value->toString(), + default => $value, + }; + } + + return $options; + } + /** * Populate the given response with additional data. * diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 6aadac7005ac..446980b88d22 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -481,6 +481,57 @@ public function toArray(): array }); } + public function testCanSendJsonDataWithStringable() + { + $this->factory->fake(); + + $this->factory->withHeaders([ + 'X-Test-Header' => 'foo', + 'X-Test-ArrayHeader' => ['bar', 'baz'], + ])->post('http://foo.com/json', [ + 'name' => Str::of('Taylor'), + ]); + + $this->factory->assertSent(function (Request $request) { + return $request->url() === 'http://foo.com/json' && + $request->hasHeader('Content-Type', 'application/json') && + $request->hasHeader('X-Test-Header', 'foo') && + $request->hasHeader('X-Test-ArrayHeader', ['bar', 'baz']) && + $request['name'] === 'Taylor'; + }); + } + + public function testCanSendFormDataWithStringable() + { + $this->factory->fake(); + + $this->factory->asForm()->post('http://foo.com/form', [ + 'name' => Str::of('Taylor'), + 'title' => 'Laravel Developer', + ]); + + $this->factory->assertSent(function (Request $request) { + return $request->url() === 'http://foo.com/form' && + $request->hasHeader('Content-Type', 'application/x-www-form-urlencoded') && + $request['name'] === 'Taylor'; + }); + } + + public function testCanSendFormDataWithStringableInArrays() + { + $this->factory->fake(); + + $this->factory->asForm()->post('http://foo.com/form', [ + 'posts' => [['title' => Str::of('Taylor')]], + ]); + + $this->factory->assertSent(function (Request $request) { + return $request->url() === 'http://foo.com/form' && + $request->hasHeader('Content-Type', 'application/x-www-form-urlencoded') && + $request['posts'][0]['title'] === 'Taylor'; + }); + } + public function testRecordedCallsAreEmptiedWhenFakeIsCalled() { $this->factory->fake([ @@ -800,6 +851,32 @@ public function testWithQueryParametersAllowsOverridingParameterOnRequest() }); } + public function testWithStringableQueryParameters() + { + $this->factory->fake(); + + $this->factory->withQueryParameters( + ['foo' => Str::of('bar'),] + )->get('https://laravel.com'); + + $this->factory->assertSent(function (Request $request) { + return $request->url() === 'https://laravel.com?foo=bar'; + }); + } + + public function testWithArrayStringableQueryParameters() + { + $this->factory->fake(); + + $this->factory->withQueryParameters( + ['foo' => ['bar', Str::of('baz')]], + )->get('https://laravel.com'); + + $this->factory->assertSent(function (Request $request) { + return $request->url() === 'https://laravel.com?foo%5B0%5D=bar&foo%5B1%5D=baz'; + }); + } + public function testGetWithArrayQueryParam() { $this->factory->fake();