8000 minor #43612 [BrowserKit][HttpClient][Routing] support building query… · symfony/symfony@a76d746 · GitHub
[go: up one dir, main page]

Skip to content

Commit a76d746

Browse files
committed
minor #43612 [BrowserKit][HttpClient][Routing] support building query strings with stringables (nicolas-grekas, OskarStark)
This PR was merged into the 5.4 branch. Discussion ---------- [BrowserKit][HttpClient][Routing] support building query strings with stringables | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes (minor) | Deprecations? | no | Tickets | Fix #26992 | License | MIT | Doc PR | - Allows using eg an instance of `Uid` as a route parameter. Replaces #42057 Commits ------- 65e2cac Add check and tests for public properties ab38bb8 [BrowserKit][HttpClient][Routing] support building query strings with stringables
2 parents 48b78fe + 65e2cac commit a76d746

File tree

7 files changed

+114
-14
lines changed

7 files changed

+114
-14
lines changed

src/Symfony/Component/BrowserKit/HttpBrowser.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,18 @@ private function getBodyAndExtraHeaders(Request $request, array $headers): array
9191
return ['', []];
9292
}
9393

94-
return [http_build_query($fields, '', '&', \PHP_QUERY_RFC1738), ['Content-Type' => 'application/x-www-form-urlencoded']];
94+
array_walk_recursive($fields, $caster = static function (&$v) use (&$caster) {
95+
if (\is_object($v)) {
96+
if ($vars = get_object_vars($v)) {
97+
array_walk_recursive($vars, $caster);
98+
$v = $vars;
99+
} elseif (method_exists($v, '__toString')) {
100+
$v = (string) $v;
101+
}
102+
}
103+
});
104+
105+
return [http_build_query($fields, '', '&'), ['Content-Type' => 'application/x-www-form-urlencoded']];
95106
}
96107

97108
protected function getHeaders(Request $request): array

src/Symfony/Component/HttpClient/HttpClientTrait.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,18 @@ private static function normalizeHeaders(array $headers): array
291291
private static function normalizeBody($body)
292292
{
293293
if (\is_array($body)) {
294-
return http_build_query($body, '', '&', \PHP_QUERY_RFC1738);
294+
array_walk_recursive($body, $caster = static function (&$v) use (&$caster) {
295+
if (\is_object($v)) {
296+
if ($vars = get_object_vars($v)) {
297+
array_walk_recursive($vars, $caster);
298+
$v = $vars;
299+
} elseif (method_exists($v, '__toString')) {
300+
$v = (string) $v;
301+
}
302+
}
303+
});
304+
305+
return http_build_query($body, '', '&');
295306
}
296307

297308
if (\is_string($body)) {

src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,4 +400,22 @@ public function testChangeResponseFactory()
400400

401401
$this->assertSame($expectedBody, $response->getContent());
402402
}
403+
404+
public function testStringableBodyParam()
405+
{
406+
$client = new MockHttpClient();
407+
408+
$param = new class() {
409+
public function __toString()
410+
{
411+
return 'bar';
412+
}
413+
};
414+
415+
$response = $client->request('GET', 'https://example.com', [
416+
'body' => ['foo' => $param],
417+
]);
418+
419+
$this->assertSame('foo=bar', $response->getRequestOptions()['body']);
420+
}
403421
}

src/Symfony/Component/Notifier/Bridge/Mercure/MercureTransport.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public function __construct(HubInterface $hub, string $hubId, $topics = null, Ht
5252

5353
public function __toString(): string
5454
{
55-
return sprintf('mercure://%s?%s', $this->hubId, http_build_query(['topic' => $this->topics]));
55+
return sprintf('mercure://%s?%s', $this->hubId, http_build_query(['topic' => $this->topics], '', '&'));
5656
}
5757

5858
public function supports(MessageInterface $message): bool

src/Symfony/Component/Routing/Generator/UrlGenerator.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,17 @@ protected function doGenerate(array $variables, array $defaults, array $requirem
295295
return $a == $b ? 0 : 1;
296296
});
297297

298+
array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) {
299+
if (\is_object($v)) {
300+
if ($vars = get_object_vars($v)) {
301+
array_walk_recursive($vars, $caster);
302+
$v = $vars;
303+
} elseif (method_exists($v, '__toString')) {
304+
$v = (string) $v;
305+
}
306+
}
307+
});
308+
298309
// extract fragment
299310
$fragment = $defaults['_fragment'] ?? '';
300311

src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,50 @@ public function testNotPassedOptionalParameterInBetween()
104104
$this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test'));
105105
}
106106

107-
public function testRelativeUrlWithExtraParameters()
107+
/**
108+
* @dataProvider valuesProvider
109+
*/
110+
public function testRelativeUrlWithExtraParameters(string $expectedQueryString, string $parameter, $value)
108111
{
109112
$routes = $this->getRoutes('test', new Route('/testing'));
110-
$url = $this->getGenerator($routes)->generate('test', ['foo' => 'bar'], UrlGeneratorInterface::ABSOLUTE_PATH);
113+
$url = $this->getGenerator($routes)->generate('test', [$parameter => $value], UrlGeneratorInterface::ABSOLUTE_PATH);
111114

112-
$this->assertEquals('/app.php/testing?foo=bar', $url);
115+
$this->assertSame('/app.php/testing'.$expectedQueryString, $url);
113116
}
114117

115-
public function testAbsoluteUrlWithExtraParameters()
118+
/**
119+
* @dataProvider valuesProvider
120+
*/
121+
public function testAbsoluteUrlWithExtraParameters(string $expectedQueryString, string $parameter, $value)
116122
{
117123
$routes = $this->getRoutes('test', new Route('/testing'));
118-
$url = $this->getGenerator($routes)->generate('test', ['foo' => 'bar'], UrlGeneratorInterface::ABSOLUTE_URL);
124+
$url = $this->getGenerator($routes)->generate('test', [$parameter => $value], UrlGeneratorInterface::ABSOLUTE_URL);
119125

120-
$this->assertEquals('http://localhost/app.php/testing?foo=bar', $url);
126+
$this->assertSame('http://localhost/app.php/testing'.$expectedQueryString, $url);
121127
}
122128

123-
public function testUrlWithNullExtraParameters()
129+
public function valuesProvider(): array
124130
{
125-
$routes = $this->getRoutes('test', new Route('/testing'));
126-
$url = $this->getGenerator($routes)->generate('test', ['foo' => null], UrlGeneratorInterface::ABSOLUTE_URL);
131+
$stdClass = new \stdClass();
132+
$stdClass->baz = 'bar';
127133

128-
$this->assertEquals('http://localhost/app.php/testing', $url);
134+
$nestedStdClass = new \stdClass();
135+
$nestedStdClass->nested = $stdClass;
136+
137+
return [
138+
'null' => ['', 'foo', null],
139+
'string' => ['?foo=bar', 'foo', 'bar'],
140+
'boolean-false' => ['?foo=0', 'foo', false],
141+
'boolean-true' => ['?foo=1', 'foo', true],
142+
'object implementing __toString()' => ['?foo=bar', 'foo', new StringableObject()],
143+
'object implementing __toString() but has public property' => ['?foo%5Bfoo%5D=property', 'foo', new StringableObjectWithPublicProperty()],
144+
'object implementing __toString() in nested array' => ['?foo%5Bbaz%5D=bar', 'foo', ['baz' => new StringableObject()]],
145+
'object implementing __toString() in nested array but has public property' => ['?foo%5Bbaz%5D%5Bfoo%5D=property', 'foo', ['baz' => new StringableObjectWithPublicProperty()]],
146+
'stdClass' => ['?foo%5Bbaz%5D=bar', 'foo', $stdClass],
147+
'stdClass in nested stdClass' => ['?foo%5Bnested%5D%5Bbaz%5D=bar', 'foo', $nestedStdClass],
148+
'non stringable object' => ['', 'foo', new NonStringableObject()],
149+
'non stringable object but has public property' => ['?foo%5Bfoo%5D=property', 'foo', new NonStringableObjectWithPublicProperty()],
150+
];
129151
}
130152

131153
public function testUrlWithExtraParametersFromGlobals()
@@ -898,3 +920,30 @@ protected function getRoutes($name, Route $route)
898920
return $routes;
899921
}
900922
}
923+
924+
class StringableObject
925+
{
926+
public function __toString()
927+
{
FDBA
928+
return 'bar';
929+
}
930+
}
931+
932+
class StringableObjectWithPublicProperty
933+
{
934+
public $foo = 'property';
935+
936+
public function __toString()
937+
{
938+
return 'bar';
939+
}
940+
}
941+
942+
class NonStringableObject
943+
{
944+
}
945+
946+
class NonStringableObjectWithPublicProperty
947+
{
948+
public $foo = 'property';
949+
}

src/Symfony/Component/Security/Http/Impersonate/ImpersonateUrlGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private function buildExitPath(string $targetUri = null): string
6969
$targetUri = $request->getRequestUri();
7070
}
7171

72-
$targetUri .= (parse_url($targetUri, \PHP_URL_QUERY) ? '&' : '?').http_build_query([$switchUserConfig['parameter'] => SwitchUserListener::EXIT_VALUE]);
72+
$targetUri .= (parse_url($targetUri, \PHP_URL_QUERY) ? '&' : '?').http_build_query([$switchUserConfig['parameter'] => SwitchUserListener::EXIT_VALUE], '', '&');
7373

7474
return $targetUri;
7575
}

0 commit comments

Comments
 (0)
0