8000 [Routing] Redirect from trailing slash to no-slash when possible · symfony/symfony@94ca7d9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 94ca7d9

Browse files
[Routing] Redirect from trailing slash to no-slash when possible
1 parent a38cbd0 commit 94ca7d9

20 files changed

+375
-393
lines changed

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

Lines changed: 68 additions & 89 deletions
Large diffs are not rendered by default.

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,6 @@ private function getCommonPrefix(string $prefix, string $anotherPrefix): array
180180
break;
181181
}
182182
}
183-
if (1 < $i && '/' === $prefix[$i - 1]) {
184-
--$i;
185-
}
186-
if (null !== $staticLength && 1 < $staticLength && '/' === $prefix[$staticLength - 1]) {
187-
--$staticLength;
188-
}
189183

190184
return array(substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i));
191185
}

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,21 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
2525
public function match($pathinfo)
2626
{
2727
try {
28-
$parameters = parent::match($pathinfo);
28+
return parent::match($pathinfo);
2929
} catch (ResourceNotFoundException $e) {
30-
if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
30+
if (!\in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
3131
throw $e;
3232
}
3333

3434
try {
35-
$parameters = parent::match($pathinfo.'/');
35+
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
36+
$ret = parent::match($pathinfo);
3637

37-
return array_replace($parameters, $this->redirect($pathinfo.'/', isset($parameters['_route']) ? $parameters['_route'] : null));
38+
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;
3839
} catch (ResourceNotFoundException $e2) {
3940
throw $e;
4041
}
4142
}
42-
43-
return $parameters;
4443
}
4544

4645
/**

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function match($rawPathinfo)
1919
{
2020
$allow = array();
2121
$pathinfo = rawurldecode($rawPathinfo);
22-
$trimmedPathinfo = rtrim($pathinfo, '/');
2322
$context = $this->context;
2423
$requestMethod = $canonicalMethod = $context->getMethod();
2524

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function match($rawPathinfo)
1919
{
2020
$allow = array();
2121
$pathinfo = rawurldecode($rawPathinfo);
22-
$trimmedPathinfo = rtrim($pathinfo, '/');
2322
$context = $this->context;
2423
$requestMethod = $canonicalMethod = $context->getMethod();
2524
$host = strtolower($context->getHost());
@@ -82,41 +81,41 @@ public function match($rawPathinfo)
8281
.'|/([^/]++)(*:57)'
8382
.'|head/([^/]++)(*:77)'
8483
.')'
85-
.'|/test/([^/]++)(?'
86-
.'|/(*:103)'
84+
.'|/test/([^/]++)/(?'
85+
.'|(*:103)'
8786
.')'
8887
.'|/([\']+)(*:119)'
89-
.'|/a(?'
90-
.'|/b\'b/([^/]++)(?'
88+
.'|/a/(?'
89+
.'|b\'b/([^/]++)(?'
9190
.'|(*:148)'
9291
.'|(*:156)'
9392
.')'
94-
.'|/(.*)(*:170)'
95-
.'|/b\'b/([^/]++)(?'
96-
.'|(*:194)'
97-
.'|(*:202)'
93+
.'|(.*)(*:169)'
94+
.'|b\'b/([^/]++)(?'
95+
.'|(*:192)'
96+
.'|(*:200)'
9897
.')'
9998
.')'
100-
.'|/multi/hello(?:/([^/]++))?(*:238)'
99+
.'|/multi/hello(?:/([^/]++))?(*:236)'
101100
.'|/([^/]++)/b/([^/]++)(?'
102-
.'|(*:269)'
103-
.'|(*:277)'
101+
.'|(*:267)'
102+
.'|(*:275)'
104103
.')'
105-
.'|/aba/([^/]++)(*:299)'
104+
.'|/aba/([^/]++)(*:297)'
106105
.')|(?i:([^\\.]++)\\.example\\.com)(?'
107106
.'|/route1(?'
108-
.'|3/([^/]++)(*:359)'
109-
.'|4/([^/]++)(*:377)'
107+
.'|3/([^/]++)(*:357)'
108+
.'|4/([^/]++)(*:375)'
110109
.')'
111110
.')|(?i:c\\.example\\.com)(?'
112-
.'|/route15/([^/]++)(*:427)'
111+
.'|/route15/([^/]++)(*:425)'
113112
.')|[^/]*+(?'
114-
.'|/route16/([^/]++)(*:462)'
115-
.'|/a(?'
116-
.'|/a\\.\\.\\.(*:483)'
117-
.'|/b(?'
118-
.'|/([^/]++)(*:505)'
119-
.'|/c/([^/]++)(*:524)'
113+
.'|/route16/([^/]++)(*:460)'
114+
.'|/a/(?'
115+
.'|a\\.\\.\\.(*:481)'
116+
.'|b/(?'
117+
.'|([^/]++)(*:502)'
118+
.'|c/([^/]++)(*:520)'
120119
.')'
121120
.')'
122121
.')'
@@ -167,14 +166,14 @@ public function match($rawPathinfo)
167166
not_foo1:
168167

169168
break;
170-
case 194:
169+
case 192:
171170
$matches = array('foo1' => $matches[1] ?? null);
172171

173172
// foo2
174173
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array());
175174

176175
break;
177-
case 269:
176+
case 267:
178177
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
179178

180179
// foo3
@@ -188,18 +187,18 @@ public function match($rawPathinfo)
188187
77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
189188
119 => array(array('_route' => 'quoter'), array('quoter'), null, null),
190189
156 => array(array('_route' => 'bar1'), array('bar'), null, null),
191-
170 => array(array('_route' => 'overridden'), array('var'), null, null),
192-
202 => array(array('_route' => 'bar2'), array('bar1'), null, null),
193-
238 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
194-
277 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
195-
299 => array(array('_route' => 'foo4'), array('foo'), null, null),
196-
359 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
197-
377 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
198-
427 => array(array('_route' => 'route15'), array('name'), null, null),
199-
462 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
200-
483 => array(array('_route' => 'a'), array(), null, null),
201-
505 => array(array('_route' => 'b'), array('var'), null, null),
202-
524 => array(array('_route' => 'c'), array('var'), null, null),
190+
169 => array(array('_route' => 'overridden'), array('var'), null, null),
191+
200 => array(array('_route' => 'bar2'), array('bar1'), null, null),
192+
236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
193+
275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
194+
297 => array(array('_route' => 'foo4'), array('foo'), null, null),
195+
357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
196+
375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
197+
425 => array(array('_route' => 'route15'), array('name'), null, null),
198+
460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
199+
481 => array(array('_route' => 'a'), array(), null, null),
200+
502 => array(array('_route' => 'b'), array('var'), null, null),
201+
520 => array(array('_route' => 'c'), array('var'), null, null),
203202
);
204203

205204
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
@@ -218,7 +217,7 @@ public function match($rawPathinfo)
218217
return $ret;
219218
}
220219

221-
if (524 === $m) {
220+
if (520 === $m) {
222221
break;
223222
}
224223
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function match($rawPathinfo)
1919
{
2020
$allow = array();
2121
$pathinfo = rawurldecode($rawPathinfo);
22-
$trimmedPathinfo = rtrim($pathinfo, '/');
2322
$context = $this->context;
2423
$requestMethod = $canonicalMethod = $context->getMethod();
2524

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,30 @@ public function __construct(RequestContext $context)
1515
$this->context = $context;
1616
}
1717

18-
public function match($rawPathinfo)
18+
public function match($pathinfo)
19+
{
20+
try {
21+
return $this->doMatch($pathinfo);
22+
} catch (ResourceNotFoundException $e) {
23+
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
24+
throw $e;
25+
}
26+
27+
try {
28+
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
29+
$ret = $this->doMatch($pathinfo);
30+
31+
return $this->redirect($pathinfo, $ret['_route']) + $ret;
32+
} catch (ResourceNotFoundException $e2) {
33+
throw $e;
34+
}
35+
}
36+
}
37+
38+
private function doMatch($rawPathinfo)
1939
{
2040
$allow = array();
2141
$pathinfo = rawurldecode($rawPathinfo);
22-
$trimmedPathinfo = rtrim($pathinfo, '/');
2342
$context = $this->context;
2443
$requestMethod = $canonicalMethod = $context->getMethod();
2544

@@ -30,32 +49,34 @@ public function match($rawPathinfo)
3049
$matchedPathinfo = $pathinfo;
3150
$regexList = array(
3251
0 => '{^(?'
33-
.'|/(en|fr)(?'
34-
.'|/admin/post(?'
35-
.'|/?(*:34)'
36-
.'|/new(*:45)'
37-
.'|/(\\d+)(?'
38-
.'|(*:61)'
39-
.'|/edit(*:73)'
40-
.'|/delete(*:87)'
52+
.'|/(en|fr)/(?'
53+
.'|admin/post/(?'
54+
.'|(*:33)'
55+
.'|new(*:43)'
56+
.'|(\\d+)(?'
57+
.'|(*:58)'
58+
.'|/(?'
59+
.'|edit(*:73)'
60+
.'|delete(*:86)'
61+
.')'
4162
.')'
4263
.')'
43-
.'|/blog(?'
44-
.'|/?(*:106)'
45-
.'|/rss\\.xml(*:123)'
46-
.'|/p(?'
47-
.'|age/([^/]++)(*:148)'
48-
.'|osts/([^/]++)(*:169)'
64+
.'|blog/(?'
65+
.'|(*:104)'
66+
.'|rss\\.xml(*:120)'
67+
.'|p(?'
68+
.'|age/([^/]++)(*:144)'
69+
.'|osts/([^/]++)(*:165)'
4970
.')'
50-
.'|/comments/(\\d+)/new(*:197)'
51-
.'|/search(*:212)'
71+
.'|comments/(\\d+)/new(*:192)'
72+
.'|search(*:206)'
5273
.')'
53-
.'|/log(?'
54-
.'|in(*:230)'
55-
.'|out(*:241)'
74+
.'|log(?'
75+
.'|in(*:223)'
76+
.'|out(*:234)'
5677
.')'
5778
.')'
58-
.'|/(en|fr)?(*:260)'
79+
.'|/(en|fr)?(*:253)'
5980
.')$}sD',
6081
);
6182

@@ -64,20 +85,20 @@ public function match($rawPathinfo)
6485
switch ($m = (int) $matches['MARK']) {
6586
default:
6687
$routes = array(
67-
34 => array(array('_route' => 'a', '_locale' => 'en'), array('_locale'), null, null, true),
68-
45 => array(array('_route' => 'b', '_locale' => 'en'), array('_locale'), null, null),
69-
61 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null),
88+
33 => array(array('_route' => 'a', '_locale' => 'en'), array('_locale'), null, null),
89+
43 => array(array('_route' => 'b', '_locale' => 'en'), array('_locale'), null, null),
90+
58 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null),
7091
73 => array(array('_route' => 'd', '_locale' => 'en'), array('_locale', 'id'), null, null),
71-
87 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null),
72-
106 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null, true),
73-
123 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null),
74-
148 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null),
75-
169 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null),
76-
197 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null),
77-
212 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null),
78-
230 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null),
79-
241 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null),
80-
260 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null),
92+
86 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null),
93+
104 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null),
94+
120 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null),
95+
144 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null),
96+
165 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null),
97+
192 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null),
98+
206 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null),
99+
223 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null),
100+
234 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null),
101+
253 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null),
81102
);
82103

83104
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
@@ -88,15 +109,6 @@ public function match($rawPathinfo)
88109
}
89110
}
90111

91-
if (empty($routes[$m][4]) || '/' === $pathinfo[-1]) {
92-
// no-op
93-
} elseif ('GET' !== $canonicalMethod) {
94-
$allow['GET'] = 'GET';
95-
break;
96-
} else {
97-
return array_replace($ret, $this->redirect($rawPathinfo.'/', $ret['_route']));
98-
}
99-
100112
if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) {
101113
if ('GET' !== $canonicalMethod) {
102114
$allow['GET'] = 'GET';
@@ -114,7 +126,7 @@ public function match($rawPathinfo)
114126
return $ret;
115127
}
116128

117-
if (260 === $m) {
129+
if (253 === $m) {
118130
break;
119131
}
120132
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function match($rawPathinfo)
1919
{
2020
$allow = array();
2121
$pathinfo = rawurldecode($rawPathinfo);
22-
$trimmedPathinfo = rtrim($pathinfo, '/');
2322
$context = $this->context;
2423
$requestMethod = $canonicalMethod = $context->getMethod();
2524

@@ -30,19 +29,19 @@ public function match($rawPathinfo)
3029
$matchedPathinfo = $pathinfo;
3130
$regexList = array(
3231
0 => '{^(?'
33-
.'|/abc([^/]++)(?'
34-
.'|/1(?'
32+
.'|/abc([^/]++)/(?'
33+
.'|1(?'
3534
.'|(*:27)'
3635
.'|0(?'
3736
.'|(*:38)'
3837
.'|0(*:46)'
3938
.')'
4039
.')'
41-
.'|/2(?'
42-
.'|(*:60)'
40+
.'|2(?'
41+
.'|(*:59)'
4342
.'|0(?'
44-
.'|(*:71)'
45-
.'|0(*:79)'
43+
.'|(*:70)'
44+
.'|0(*:78)'
4645
.')'
4746
.')'
4847
.')'
@@ -57,9 +56,9 @@ public function match($rawPathinfo)
5756
27 => array(array('_route' => 'r1'), array('foo'), null, null),
5857
38 => array(array('_route' => 'r10'), array('foo'), null, null),
5958
46 => array(array('_route' => 'r100'), array('foo'), null, null),
60-
60 => array(array('_route' => 'r2'), array('foo'), null, null),
61-
71 => array(array('_route' => 'r20'), array('foo'), null, null),
62-
79 => array(array('_route' => 'r200'), array('foo'), null, null),
59+
59 => array(array('_route' => 'r2'), array('foo'), null, null),
60+
70 => array(array('_route' => 'r20'), array('foo'), null, null),
61+
78 => array(array('_route' => 'r200'), array('foo'), null, null),
6362
);
6463

6564
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
@@ -78,7 +77,7 @@ public function match($rawPathinfo)
7877
return $ret;
7978
}
8079

81-
if (79 === $m) {
80+
if (78 === $m) {
8281
break;
8382
}
8483
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function match($rawPathinfo)
1919
{
2020
$allow = array();
2121
$pathinfo = rawurldecode($rawPathinfo);
22-
$trimmedPathinfo = rtrim($pathinfo, '/');
2322
$context = $this->context;
2423
$requestMethod = $canonicalMethod = $context->getMethod();
2524
$host = strtolower($context->getHost());

0 commit comments

Comments
 (0)
0