8000 [Routing] check static routes first in a switch · symfony/symfony@f0b7433 · GitHub
[go: up one dir, main page]

Skip to content

Commit f0b7433

Browse files
[Routing] check static routes first in a switch
1 parent 2ef0d60 commit f0b7433

File tree

9 files changed

+643
-606
lines changed

9 files changed

+643
-606
lines changed

src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Routing\Generator\Dumper;
1313

14+
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
15+
1416
/**
1517
* PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes.
1618
*
@@ -88,7 +90,7 @@ private function generateDeclaredRoutes()
8890
$properties[] = $compiledRoute->getHostTokens();
8991
$properties[] = $route->getSchemes();
9092

91-
$routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true)));
93+
$routes .= sprintf(" '%s' => %s,\n", $name, PhpMatcherDumper::export($properties));
9294
}
9395
$routes .= ' )';
9496

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

Lines changed: 118 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
140140
$code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true));
141141
}
142142

143+
$groupCode = $this->compileStaticRoutes($collection, $supportsRedirections);
143144
$tree = $this->buildStaticPrefixCollection($collection);
144-
$groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections);
145+
$groupCode .= $this->compileStaticPrefixRoutes($tree, $supportsRedirections);
145146

146147
if (null !== $regex) {
147148
// apply extra indention at each line (except empty ones)
@@ -176,6 +177,66 @@ private function buildStaticPrefixCollection(DumperCollection $collection)
176177
return $prefixCollection;
177178
}
178179

180+
/**
181+
* Generates PHP code to match the static routes in a collection.
182+
*/
183+
private function compileStaticRoutes(DumperCollection $collection, bool $supportsRedirections): string
184+
{
185+
$code = '';
186+
$dynamicRegex = array();
187+
$dynamicRoutes = array();
188+
$staticRoutes = array();
189+
190+
foreach ($collection->all() as $route) {
191+
$compiledRoute = $route->getRoute()->compile();
192+
$regex = $compiledRoute->getRegex();
193+
$methods = $route->getRoute()->getMethods();
194+
if ($hasTrailingSlash = $supportsRedirections && $pos = strpos($regex, '/$')) {
195+
$regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
196+
}
197+
if (!$compiledRoute->getPathVariables()) {
198+
$url = $route->getRoute()->getPath();
199+
if ($hasTrailingSlash) {
200+
$url = rtrim($url, '/');
201+
}
202+
foreach ($dynamicRegex as $rx) {
203+
if (preg_match($rx, $url)) {
204+
$dynamicRegex[] = $regex;
205+
$dynamicRoutes[] = $route;
206+
continue 2;
207+
}
208+
}
209+
210+
$staticRoutes[$url][] = array($hasTrailingSlash, $route);
211+
} else {
212+
$dynamicRegex[] = $regex;
213+
$dynamicRoutes[] = $route;
214+
}
215+
}
216+
217+
$collection->setAll($dynamicRoutes);
218+
219+
if ($staticRoutes) {
220+
foreach ($staticRoutes as $url => $routes) {
221+
$code .= sprintf(" case %s:\n", var_export($url, true));
222+
foreach ($routes as list($hasTrailingSlash, $route)) {
223+
$methods = $route->getRoute()->getMethods();
224+
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods) || in_array('GET', $methods));
225+
$routeCode = $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, null, $hasTrailingSlash);
226+
if ($route->getRoute()->getCondition() || ($hasTrailingSlash && !$supportsTrailingSlash)) {
227+
$routeCode = preg_replace('/^.{2,}$/m', ' $0', $routeCode);
228+
}
229+
$code .= $routeCode;
230+
}
231+
$code .= " break;\n";
232+
}
233+
$code = preg_replace('/^.{2,}$/m', ' $0', $code);
234+
$code = sprintf(" switch (%s) {\n{$code} }\n\n", $supportsRedirections ? '$trimmedPathinfo' : '$pathinfo');
235+
}
236+
237+
return $code;
238+
}
239+
179240
/**
180241
* Generates PHP code to match a tree of routes.
181242
*
@@ -222,28 +283,29 @@ private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $
222283
* @param string $name The name of the Route
223284
* @param bool $supportsRedirections Whether redirections are supported by the base class
224285
* @param string|null $parentPrefix The prefix of the parent collection used to optimize the code
286+
* @param bool|null $hasTrailingSlash Whether the path has a trailing slash when compiling a static route
225287
*
226288
* @return string PHP code
227289
*
228290
* @throws \LogicException
229291
*/
230-
private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null)
292+
private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null, bool $hasTrailingSlash = null)
231293
{
232294
$code = '';
233295
$compiledRoute = $route->compile();
234296
$conditions = array();
235-
$hasTrailingSlash = false;
236297
$matches = false;
237298
$hostMatches = false;
238299
$methods = $route->getMethods();
239300

240301
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods) || in_array('GET', $methods));
241302
$regex = $compiledRoute->getRegex();
242303

243-
if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#'.('u' === substr($regex, -1) ? 'u' : ''), $regex, $m)) {
244-
if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) {
304+
if (null !== $hasTrailingSlash && (!$hasTrailingSlash || $supportsTrailingSlash)) {
305+
// no-op
306+
} elseif (!$compiledRoute->getPathVariables() && preg_match('#^(.)\^(?P<url>.*?)\$\1#'.('u' === $regex[-1] ? 'u' : ''), $regex, $m)) {
307+
if ($hasTrailingSlash = $supportsTrailingSlash && '/' === $m['url'][-1]) {
245308
$conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
246-
$hasTrailingSlash = true;
247309
} else {
248310
$conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true));
249311
}
@@ -252,9 +314,8 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
252314
$conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true));
253315
}
254316

255-
if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) {
317+
if ($hasTrailingSlash = $supportsTrailingSlash && $pos = strpos($regex, '/$')) {
256318
$regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
257-
$hasTrailingSlash = true;
258319
}
259320
$conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true));
260321

@@ -271,11 +332,15 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
271332

272333
$conditions = implode(' && ', $conditions);
273334

274-
$code .= <<<EOF
335+
if ($conditions) {
336+
$code .= <<<EOF
275337
// $name
276338
if ($conditions) {
277339
278340
EOF;
341+
} else {
342+
$code .= " // {$name}\n";
343+
}
279344

280345
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
281346

@@ -349,17 +414,17 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
349414
$code .= sprintf(
350415
" \$ret = \$this->mergeDefaults(array_replace(%s), %s);\n",
351416
implode(', ', $vars),
352-
str_replace("\n", '', var_export($route->getDefaults(), true))
417+
self::export($route->getDefaults())
353418
);
354419
} elseif ($route->getDefaults()) {
355-
$code .= sprintf(" \$ret = %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
420+
$code .= sprintf(" \$ret = %s;\n", self::export(array_replace($route->getDefaults(), array('_route' => $name))));
356421
} else {
357422
$code .= sprintf(&q 106D2 uot; \$ret = array('_route' => '%s');\n", $name);
358423
}
359424

360425
if ($hasTrailingSlash) {
361426
$code .= <<<EOF
362-
if ('/' === substr(\$pathinfo, -1)) {
427+
if ('/' === \$pathinfo[-1]) {
363428
// no-op
364429
} elseif ('GET' !== \$canonicalMethod) {
365430
goto $gotoname;
@@ -375,7 +440,7 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
375440
if (!$supportsRedirections) {
376441
throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
377442
}
378-
$schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
443+
$schemes = self::export(array_flip($schemes));
379444
$code .= <<<EOF
380445
\$requiredSchemes = $schemes;
381446
if (!isset(\$requiredSchemes[\$context->getScheme()])) {
@@ -391,7 +456,11 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
391456
} else {
392457
$code = substr_replace($code, 'return', $retOffset, 6);
393458
}
394-
$code .= " }\n";
459+
if ($conditions) {
460+
$code .= " }\n";
461+
} elseif ($methods || $hasTrailingSlash) {
462+
$code .= ' ';
463+
}
395464

396465
if ($methods || $hasTrailingSlash) {
397466
$code .= " $gotoname:\n";
@@ -440,4 +509,39 @@ private function getExpressionLanguage()
440509

441510
return $this->expressionLanguage;
442511
}
512+
513+
/**
514+
* @internal
515+
*/
516+
public static function export($value): string
517+
{
518+
if (null === $value) {
519+
return 'null';
520+
}
521+
if (!\is_array($value)) {
522+
return var_export($value, true);
523+
}
524+
if (!$value) {
525+
return 'array()';
526+
}
527+
528+
$i = 0;
529+
$export = 'array(';
530+
531+
foreach ($value as $k => $v) {
532+
if ($i === $k) {
533+
++$i;
534+
} else {
535+
$export .= var_export($k, true).' => ';
536+
537+
if (\is_int($k) && $i < $k) {
538+
$i = 1 + $k;
539+
}
540+
}
541+
542+
$export .= self::export($v).', ';
543+
}
544+
545+
return substr_replace($export, ')', -2);
546+
}
443547
}

0 commit comments

Comments
 (0)
0