Skip to content

Commit ae3b078

Browse files
committed
feature #45054 [Routing] Allow using UTF-8 parameter names (nicolas-grekas)
This PR was merged into the 6.1 branch. Discussion ---------- [Routing] Allow using UTF-8 parameter names | Q | A | ------------- | --- | Branch? | 6.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #41909 | License | MIT | Doc PR | - Commits ------- dfd852d [Routing] Allow using UTF-8 parameter names
2 parents 0a92ead + dfd852d commit ae3b078

File tree

8 files changed

+30
-9
lines changed

8 files changed

+30
-9
lines changed

src/Symfony/Component/Routing/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.1
5+
---
6+
7+
* Allow using UTF-8 parameter names
8+
49
5.3
510
---
611

src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo
332332
if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) {
333333
$regex = substr($regex, 0, -1);
334334
}
335-
$hasTrailingVar = (bool) preg_match('#\{\w+\}/?$#', $route->getPath());
335+
$hasTrailingVar = (bool) preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
336336

337337
$tree->addRoute($regex, [$name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar]);
338338
}

src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a
9999
continue;
100100
}
101101

102-
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
102+
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
103103

104104
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
105105
if ($hasTrailingSlash) {

src/Symfony/Component/Routing/Matcher/UrlMatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a
152152
continue;
153153
}
154154

155-
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
155+
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{[\w\x80-\xFF]+\}/?$#', $route->getPath());
156156

157157
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
158158
if ($hasTrailingSlash) {

src/Symfony/Component/Routing/Route.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ private function extractInlineDefaultsAndRequirements(string $pattern): string
416416
return $pattern;
417417
}
418418

419-
return preg_replace_callback('#\{(!?)(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
419+
return preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
420420
if (isset($m[4][0])) {
421421
$this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
422422
}

src/Symfony/Component/Routing/RouteCompiler.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private static function compilePattern(Route $route, string $pattern, bool $isHo
117117

118118
// Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
119119
// in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
120-
preg_match_all('#\{(!)?(\w+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
120+
preg_match_all('#\{(!)?([\w\x80-\xFF]+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
121121
foreach ($matches as $match) {
122122
$important = $match[1][1] >= 0;
123123
$varName = $match[2][0];
@@ -170,7 +170,7 @@ private static function compilePattern(Route $route, string $pattern, bool $isHo
170170
preg_quote($defaultSeparator),
171171
$defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator) : ''
172172
);
173-
if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
173+
if (('' !== $nextSeparator && !preg_match('#^\{[\w\x80-\xFF]+\}#', $followingPattern)) || '' === $followingPattern) {
174174
// When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
175175
// quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
176176
// Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
@@ -271,7 +271,7 @@ private static function findNextSeparator(string $pattern, bool $useUtf8): strin
271271
return '';
272272
}
273273
// first remove all placeholders from the pattern so we can find the next real static character
274-
if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) {
274+
if ('' === $pattern = preg_replace('#\{[\w\x80-\xFF]+\}#', '', $pattern)) {
275275
return '';
276276
}
277277
if ($useUtf8) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,12 @@ public function provideLookAroundRequirementsInPath()
10121012
yield ['/app.php/bar/a/b/bam/c/d/e', '/bar/{foo}/bam/{baz}', '(?<!^).+'];
10131013
}
10141014

1015+
public function testUtf8VarName()
1016+
{
1017+
$routes = $this->getRoutes('test', new Route('/foo/{bär}', [], [], ['utf8' => true]));
1018+
$this->assertSame('/app.php/foo/baz', $this->getGenerator($routes)->generate('test', ['bär' => 'baz']));
1019+
}
1020+
10151021
protected function getGenerator(RouteCollection $routes, array $parameters = [], $logger = null, string $defaultLocale = null)
10161022
{
10171023
$context = new RequestContext('/app.php');

src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,10 +833,10 @@ public function testSlashVariant()
833833
public function testSlashVariant2()
834834
{
835835
$coll = new RouteCollection();
836-
$coll->add('a', new Route('/foo/{bar}/', [], ['bar' => '.*']));
836+
$coll->add('a', new Route('/foo/{bär}/', [], ['bär' => '.*'], ['utf8' => true]));
837837

838838
$matcher = $this->getUrlMatcher($coll);
839-
$this->assertEquals(['_route' => 'a', 'bar' => 'bar'], $matcher->match('/foo/bar/'));
839+
$this->assertEquals(['_route' => 'a', 'bär' => 'bar'], $matcher->match('/foo/bar/'));
840840
}
841841

842842
public function testSlashWithVerb()
@@ -941,6 +941,16 @@ public function testRestrictiveTrailingRequirementWithStaticRouteAfter()
941941
$this->assertEquals(['_route' => 'a', '_' => '/'], $matcher->match('/hello/'));
942942
}
943943

944+
public function testUtf8VarName()
945+
{
946+
$collection = new RouteCollection();
947+
$collection->add('foo', new Route('/foo/{bär}/{bäz?foo}', [], [], ['utf8' => true]));
948+
949+
$matcher = $this->getUrlMatcher($collection);
950+
951+
$this->assertEquals(['_route' => 'foo', 'bär' => 'baz', 'bäz' => 'foo'], $matcher->match('/foo/baz'));
952+
}
953+
944954
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
945955
{
946956
return new UrlMatcher($routes, $context ?? new RequestContext());

0 commit comments

Comments
 (0)