Skip to content

Commit 0294be7

Browse files
[10.x] Fix Stringable objects not converted to string in HTTP facade Query parameters and Body (#48849)
* Update PendingRequest.php * Add Test * Fix tests and code * Minor tidy * Support for nested arrays of Stringable + tests * refactor --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 0aeecec commit 0294be7

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed

src/Illuminate/Http/Client/PendingRequest.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Illuminate\Support\Arr;
2020
use Illuminate\Support\Collection;
2121
use Illuminate\Support\Str;
22+
use Illuminate\Support\Stringable;
2223
use Illuminate\Support\Traits\Conditionable;
2324
use Illuminate\Support\Traits\Macroable;
2425
use JsonSerializable;
@@ -1035,10 +1036,12 @@ protected function sendRequest(string $method, string $url, array $options = [])
10351036
$this->transferStats = $transferStats;
10361037
};
10371038

1038-
return $this->buildClient()->$clientMethod($method, $url, $this->mergeOptions([
1039+
$mergedOptions = $this->normalizeRequestOptions($this->mergeOptions([
10391040
'laravel_data' => $laravelData,
10401041
'on_stats' => $onStats,
10411042
], $options));
1043+
1044+
return $this->buildClient()->$clientMethod($method, $url, $mergedOptions);
10421045
}
10431046

10441047
/**
@@ -1076,6 +1079,25 @@ protected function parseRequestData($method, $url, array $options)
10761079
return is_array($laravelData) ? $laravelData : [];
10771080
}
10781081

1082+
/**
1083+
* Normalize the given request options.
1084+
*
1085+
* @param array $options
1086+
* @return array
1087+
*/
1088+
protected function normalizeRequestOptions(array $options)
1089+
{
1090+
foreach ($options as $key => $value) {
1091+
$options[$key] = match (true) {
1092+
is_array($value) => $this->normalizeRequestOptions($value),
1093+
$value instanceof Stringable => $value->toString(),
1094+
default => $value,
1095+
};
1096+
}
1097+
1098+
return $options;
1099+
}
1100+
10791101
/**
10801102
* Populate the given response with additional data.
10811103
*

tests/Http/HttpClientTest.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,57 @@ public function toArray(): array
481481
});
482482
}
483483

484+
public function testCanSendJsonDataWithStringable()
485+
{
486+
$this->factory->fake();
487+
488+
$this->factory->withHeaders([
489+
'X-Test-Header' => 'foo',
490+
'X-Test-ArrayHeader' => ['bar', 'baz'],
491+
])->post('http://foo.com/json', [
492+
'name' => Str::of('Taylor'),
493+
]);
494+
495+
$this->factory->assertSent(function (Request $request) {
496+
return $request->url() === 'http://foo.com/json' &&
497+
$request->hasHeader('Content-Type', 'application/json') &&
498+
$request->hasHeader('X-Test-Header', 'foo') &&
499+
$request->hasHeader('X-Test-ArrayHeader', ['bar', 'baz']) &&
500+
$request['name'] === 'Taylor';
501+
});
502+
}
503+
504+
public function testCanSendFormDataWithStringable()
505+
{
506+
$this->factory->fake();
507+
508+
$this->factory->asForm()->post('http://foo.com/form', [
509+
'name' => Str::of('Taylor'),
510+
'title' => 'Laravel Developer',
511+
]);
512+
513+
$this->factory->assertSent(function (Request $request) {
514+
return $request->url() === 'http://foo.com/form' &&
515+
$request->hasHeader('Content-Type', 'application/x-www-form-urlencoded') &&
516+
$request['name'] === 'Taylor';
517+
});
518+
}
519+
520+
public function testCanSendFormDataWithStringableInArrays()
521+
{
522+
$this->factory->fake();
523+
524+
$this->factory->asForm()->post('http://foo.com/form', [
525+
'posts' => [['title' => Str::of('Taylor')]],
526+
]);
527+
528+
$this->factory->assertSent(function (Request $request) {
529+
return $request->url() === 'http://foo.com/form' &&
530+
$request->hasHeader('Content-Type', 'application/x-www-form-urlencoded') &&
531+
$request['posts'][0]['title'] === 'Taylor';
532+
});
533+
}
534+
484535
public function testRecordedCallsAreEmptiedWhenFakeIsCalled()
485536
{
486537
$this->factory->fake([
@@ -800,6 +851,32 @@ public function testWithQueryParametersAllowsOverridingParameterOnRequest()
800851
});
801852
}
802853

854+
public function testWithStringableQueryParameters()
855+
{
856+
$this->factory->fake();
857+
858+
$this->factory->withQueryParameters(
859+
['foo' => Str::of('bar'),]
860+
)->get('https://laravel.com');
861+
862+
$this->factory->assertSent(function (Request $request) {
863+
return $request->url() === 'https://laravel.com?foo=bar';
864+
});
865+
}
866+
867+
public function testWithArrayStringableQueryParameters()
868+
{
869+
$this->factory->fake();
870+
871+
$this->factory->withQueryParameters(
872+
['foo' => ['bar', Str::of('baz')]],
873+
)->get('https://laravel.com');
874+
875+
$this->factory->assertSent(function (Request $request) {
876+
return $request->url() === 'https://laravel.com?foo%5B0%5D=bar&foo%5B1%5D=baz';
877+
});
878+
}
879+
803880
public function testGetWithArrayQueryParam()
804881
{
805882
$this->factory->fake();

0 commit comments

Comments
 (0)