From 9cd8dd37a5b04b1e2f2a111fc6f3d7b4337f5ed8 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 22 May 2025 09:35:49 +0200 Subject: [PATCH 1/3] Allow query-specific parameters in URL generator using `_query` --- CHANGELOG.md | 5 ++ Generator/UrlGenerator.php | 13 +++++ Tests/Generator/UrlGeneratorTest.php | 74 ++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d21e550f..4ef96d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + + * Allow query-specific parameters in `UrlGenerator` using `_query` + 7.3 --- diff --git a/Generator/UrlGenerator.php b/Generator/UrlGenerator.php index 216b0d54..d82b9189 100644 --- a/Generator/UrlGenerator.php +++ b/Generator/UrlGenerator.php @@ -142,6 +142,18 @@ public function generate(string $name, array $parameters = [], int $referenceTyp */ protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = []): string { + $queryParameters = []; + + if (isset($parameters['_query'])) { + if (\is_array($parameters['_query'])) { + $queryParameters = $parameters['_query']; + unset($parameters['_query']); + } else { + trigger_deprecation('symfony/routing', '7.4', 'Parameter "_query" is reserved for passing an array of query parameters. Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.'); + // throw new InvalidParameterException('Parameter "_query" must be an array of query parameters.'); + } + } + $variables = array_flip($variables); $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); @@ -260,6 +272,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem // add a query string if needed $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, fn ($a, $b) => $a == $b ? 0 : 1); + $extra = array_merge($extra, $queryParameters); array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) { if (\is_object($v)) { diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 25a4c674..27af7679 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -1054,6 +1054,80 @@ public function testUtf8VarName() $this->assertSame('/app.php/foo/baz', $this->getGenerator($routes)->generate('test', ['bär' => 'baz'])); } + public function testQueryParameters() + { + $routes = $this->getRoutes('user', new Route('/user/{username}')); + $url = $this->getGenerator($routes)->generate('user', [ + 'username' => 'john', + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'baz', + '_query' => [ + 'a' => '123', + 'd' => '789', + ], + ]); + $this->assertSame('/app.php/user/john?a=123&b=bar&c=baz&d=789', $url); + } + + public function testRouteHostParameterAndQueryParameterWithSameName() + { + $routes = $this->getRoutes('admin_stats', new Route('/admin/stats', requirements: ['domain' => '.+'], host: '{siteCode}.{domain}')); + $url = $this->getGenerator($routes)->generate('admin_stats', [ + 'siteCode' => 'fr', + 'domain' => 'example.com', + '_query' => [ + 'siteCode' => 'us', + ], + ], UrlGeneratorInterface::NETWORK_PATH); + $this->assertSame('//fr.example.com/app.php/admin/stats?siteCode=us', $url); + } + + public function testRoutePathParameterAndQueryParameterWithSameName() + { + $routes = $this->getRoutes('user', new Route('/user/{id}')); + $url = $this->getGenerator($routes)->generate('user', [ + 'id' => '123', + '_query' => [ + 'id' => '456', + ], + ]); + $this->assertSame('/app.php/user/123?id=456', $url); + } + + public function testQueryParameterCannotSubstituteRouteParameter() + { + $routes = $this->getRoutes('user', new Route('/user/{id}')); + + $this->expectException(MissingMandatoryParametersException::class); + $this->expectExceptionMessage('Some mandatory parameters are missing ("id") to generate a URL for route "user".'); + + $this->getGenerator($routes)->generate('user', [ + '_query' => [ + 'id' => '456', + ], + ]); + } + + /** + * @group legacy + */ + public function testQueryParametersWithScalarValue() + { + $routes = $this->getRoutes('user', new Route('/user/{id}')); + + $this->expectDeprecation( + 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. ' . + 'Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.', + ); + + $url = $this->getGenerator($routes)->generate('user', [ + 'id' => '123', + '_query' => 'foo', + ]); + $this->assertSame('/app.php/user/123?_query=foo', $url); + } + protected function getGenerator(RouteCollection $routes, array $parameters = [], $logger = null, ?string $defaultLocale = null) { $context = new RequestContext('/app.php'); From c71567120c75759c0803673129b0aa2c0684afe6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Jun 2025 16:08:14 +0200 Subject: [PATCH 2/3] Allow Symfony ^8.0 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 59e30bef..1fcc24b6 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,11 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/config": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "psr/log": "^1|^2|^3" }, "conflict": { From 1366ed8a23adefd546ceb975f70ad6a16520120c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 14 Jun 2025 22:01:30 +0200 Subject: [PATCH 3/3] replace expectDeprecation() with expectUserDeprecationMessage() --- Tests/Generator/UrlGeneratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 27af7679..75196bd2 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -1116,7 +1116,7 @@ public function testQueryParametersWithScalarValue() { $routes = $this->getRoutes('user', new Route('/user/{id}')); - $this->expectDeprecation( + $this->expectUserDeprecationMessage( 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. ' . 'Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.', );