From 69a4e941309423788890fcbd52e7848c0a547c53 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 23 Feb 2018 11:41:39 +0100 Subject: [PATCH] [Routing] Redirect from trailing slash to no-slash when possible --- .../Matcher/Dumper/PhpMatcherDumper.php | 172 +++++++---------- .../Matcher/Dumper/StaticPrefixCollection.php | 6 - .../Matcher/RedirectableUrlMatcher.php | 11 +- .../Tests/Fixtures/dumper/url_matcher0.php | 1 - .../Tests/Fixtures/dumper/url_matcher1.php | 85 +++++---- .../Tests/Fixtures/dumper/url_matcher10.php | 1 - .../Tests/Fixtures/dumper/url_matcher11.php | 102 +++++----- .../Tests/Fixtures/dumper/url_matcher12.php | 21 +-- .../Tests/Fixtures/dumper/url_matcher13.php | 5 +- .../Tests/Fixtures/dumper/url_matcher2.php | 177 +++++++++--------- .../Tests/Fixtures/dumper/url_matcher3.php | 1 - .../Tests/Fixtures/dumper/url_matcher4.php | 1 - .../Tests/Fixtures/dumper/url_matcher5.php | 58 +++--- .../Tests/Fixtures/dumper/url_matcher6.php | 37 ++-- .../Tests/Fixtures/dumper/url_matcher7.php | 95 +++++----- .../Tests/Fixtures/dumper/url_matcher8.php | 1 - .../Tests/Fixtures/dumper/url_matcher9.php | 5 +- .../DumpedRedirectableUrlMatcherTest.php | 8 - .../Dumper/StaticPrefixCollectionTest.php | 18 +- .../Matcher/RedirectableUrlMatcherTest.php | 10 + 20 files changed, 387 insertions(+), 428 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index 46987bf1f5f49..8972b6268c086 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Matcher\Dumper; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; @@ -28,6 +29,7 @@ class PhpMatcherDumper extends MatcherDumper { private $expressionLanguage; private $signalingException; + private $supportsRedirections; /** * @var ExpressionFunctionProviderInterface[] @@ -55,7 +57,7 @@ public function dump(array $options = array()) // trailing slash support is only enabled if we know how to redirect the user $interfaces = class_implements($options['base_class']); - $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + $this->supportsRedirections = isset($interfaces[RedirectableUrlMatcherInterface::class]); return <<context = \$context; } -{$this->generateMatchMethod($supportsRedirections)} +{$this->generateMatchMethod()} } EOF; @@ -89,7 +91,7 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac /** * Generates the code for the match method implementing UrlMatcherInterface. */ - private function generateMatchMethod(bool $supportsRedirections): string + private function generateMatchMethod(): string { // Group hosts by same-suffix, re-order when possible $matchHost = false; @@ -104,15 +106,13 @@ private function generateMatchMethod(bool $supportsRedirections): string } $routes = $matchHost ? $routes->populateCollection(new RouteCollection()) : $this->getRoutes(); - $code = rtrim($this->compileRoutes($routes, $supportsRedirections, $matchHost), "\n"); + $code = rtrim($this->compileRoutes($routes, $matchHost), "\n"); $fetchHost = $matchHost ? " \$host = strtolower(\$context->getHost());\n" : ''; - return <<context; \$requestMethod = \$canonicalMethod = \$context->getMethod(); {$fetchHost} @@ -122,25 +122,49 @@ public function match(\$rawPathinfo) $code - throw \$allow ? new MethodNotAllowedException(array_keys(\$allow)) : new ResourceNotFoundException(); - } EOF; + + if ($this->supportsRedirections) { + return <<<'EOF' + public function match($pathinfo) + { + $allow = array(); + if ($ret = $this->doMatch($pathinfo, $allow)) { + return $ret; + } + if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) { + $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1); + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + } + + throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + } + + private function doMatch(string $rawPathinfo, array &$allow = array()): ?array + +EOF + .$code."\n return null;\n }"; + } + + return " public function match(\$rawPathinfo)\n".$code."\n throw \$allow ? new MethodNotAllowedException(array_keys(\$allow)) : new ResourceNotFoundException();\n }"; } /** * Generates PHP code to match a RouteCollection with all its routes. */ - private function compileRoutes(RouteCollection $routes, bool $supportsRedirections, bool $matchHost): string + private function compileRoutes(RouteCollection $routes, bool $matchHost): string { - list($staticRoutes, $dynamicRoutes) = $this->groupStaticRoutes($routes, $supportsRedirections); + list($staticRoutes, $dynamicRoutes) = $this->groupStaticRoutes($routes); - $code = $this->compileStaticRoutes($staticRoutes, $supportsRedirections, $matchHost); + $code = $this->compileStaticRoutes($staticRoutes, $matchHost); $chunkLimit = count($dynamicRoutes); while (true) { try { $this->signalingException = new \RuntimeException('preg_match(): Compilation failed: regular expression is too large'); - $code .= $this->compileDynamicRoutes($dynamicRoutes, $supportsRedirections, $matchHost, $chunkLimit); + $code .= $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit); break; } catch (\Exception $e) { if (1 < $chunkLimit && $this->signalingException === $e) { @@ -163,7 +187,7 @@ private function compileRoutes(RouteCollection $routes, bool $supportsRedirectio /** * Splits static routes from dynamic routes, so that they can be matched first, using a simple switch. */ - private function groupStaticRoutes(RouteCollection $collection, bool $supportsRedirections): array + private function groupStaticRoutes(RouteCollection $collection): array { $staticRoutes = $dynamicRegex = array(); $dynamicRoutes = new RouteCollection(); @@ -172,15 +196,9 @@ private function groupStaticRoutes(RouteCollection $collection, bool $supportsRe $compiledRoute = $route->compile(); $hostRegex = $compiledRoute->getHostRegex(); $regex = $compiledRoute->getRegex(); - if ($hasTrailingSlash = $supportsRedirections && $pos = strpos($regex, '/$')) { - $regex = substr_replace($regex, '/?$', $pos, 2); - } if (!$compiledRoute->getPathVariables()) { $host = !$compiledRoute->getHostVariables() ? $route->getHost() : ''; $url = $route->getPath(); - if ($hasTrailingSlash) { - $url = rtrim($url, '/'); - } foreach ($dynamicRegex as list($hostRx, $rx)) { if (preg_match($rx, $url) && (!$host || !$hostRx || preg_match($hostRx, $host))) { $dynamicRegex[] = array($hostRegex, $regex); @@ -189,7 +207,7 @@ private function groupStaticRoutes(RouteCollection $collection, bool $supportsRe } } - $staticRoutes[$url][$name] = array($hasTrailingSlash, $route); + $staticRoutes[$url][$name] = $route; } else { $dynamicRegex[] = array($hostRegex, $regex); $dynamicRoutes->add($name, $route); @@ -207,60 +225,56 @@ private function groupStaticRoutes(RouteCollection $collection, bool $supportsRe * * @throws \LogicException */ - private function compileStaticRoutes(array $staticRoutes, bool $supportsRedirections, bool $matchHost): string + private function compileStaticRoutes(array $staticRoutes, bool $matchHost): string { if (!$staticRoutes) { return ''; } $code = $default = ''; - $checkTrailingSlash = false; foreach ($staticRoutes as $url => $routes) { if (1 === count($routes)) { - foreach ($routes as $name => list($hasTrailingSlash, $route)) { + foreach ($routes as $name => $route) { } if (!$route->getCondition()) { - if (!$supportsRedirections && $route->getSchemes()) { + if (!$this->supportsRedirections && $route->getSchemes()) { throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); } - $checkTrailingSlash = $checkTrailingSlash || $hasTrailingSlash; $default .= sprintf( "%s => array(%s, %s, %s, %s),\n", self::export($url), self::export(array('_route' => $name) + $route->getDefaults()), self::export(!$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex() ?: null), self::export(array_flip($route->getMethods()) ?: null), - self::export(array_flip($route->getSchemes()) ?: null).($hasTrailingSlash ? ', true' : '') + self::export(array_flip($route->getSchemes()) ?: null) ); continue; } } $code .= sprintf(" case %s:\n", self::export($url)); - foreach ($routes as $name => list($hasTrailingSlash, $route)) { - $code .= $this->compileRoute($route, $name, $supportsRedirections, $hasTrailingSlash, true); + foreach ($routes as $name => $route) { + $code .= $this->compileRoute($route, $name, true); } $code .= " break;\n"; } - $matchedPathinfo = $supportsRedirections ? '$trimmedPathinfo' : '$pathinfo'; - if ($default) { $code .= <<indent($default, 4)} ); - if (!isset(\$routes[{$matchedPathinfo}])) { + if (!isset(\$routes[\$pathinfo])) { break; } - list(\$ret, \$requiredHost, \$requiredMethods, \$requiredSchemes) = \$routes[{$matchedPathinfo}]; -{$this->compileSwitchDefault(false, $matchedPathinfo, $matchHost, $supportsRedirections, $checkTrailingSlash)} + list(\$ret, \$requiredHost, \$requiredMethods, \$requiredSchemes) = \$routes[\$pathinfo]; +{$this->compileSwitchDefault(false, $matchHost)} EOF; } - return sprintf(" switch (%s) {\n%s }\n\n", $matchedPathinfo, $this->indent($code)); + return sprintf(" switch (\$pathinfo) {\n%s }\n\n", $this->indent($code)); } /** @@ -281,7 +295,7 @@ private function compileStaticRoutes(array $staticRoutes, bool $supportsRedirect * matching-but-failing subpattern is blacklisted by replacing its name by "(*F)", which forces a failure-to-match. * To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur. */ - private function compileDynamicRoutes(RouteCollection $collection, bool $supportsRedirections, bool $matchHost, int $chunkLimit): string + private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit): string { if (!$collection->all()) { return ''; @@ -293,8 +307,6 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $support 'default' => '', 'mark' => 0, 'markTail' => 0, - 'supportsRedirections' => $supportsRedirections, - 'checkTrailingSlash' => false, 'hostVars' => array(), 'vars' => array(), ); @@ -392,7 +404,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $support {$this->indent($state->default, 4)} ); list(\$ret, \$vars, \$requiredMethods, \$requiredSchemes) = \$routes[\$m]; -{$this->compileSwitchDefault(true, '$m', $matchHost, $supportsRedirections, $state->checkTrailingSlash)} +{$this->compileSwitchDefault(true, $matchHost)} EOF; } @@ -448,32 +460,28 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st list($name, $regex, $vars, $route) = $route; $compiledRoute = $route->compile(); - $hasTrailingSlash = $state->supportsRedirections && '' !== $regex && '/' === $regex[-1]; if ($compiledRoute->getRegex() === $prevRegex) { - $state->switch = substr_replace($state->switch, $this->compileRoute($route, $name, $state->supportsRedirections, $hasTrailingSlash, false)."\n", -19, 0); + $state->switch = substr_replace($state->switch, $this->compileRoute($route, $name, false)."\n", -19, 0); continue; } - $methods = array_flip($route->getMethods()); - $hasTrailingSlash = $hasTrailingSlash && (!$methods || isset($methods['GET'])); - $state->mark += 3 + $state->markTail + $hasTrailingSlash + strlen($regex) - $prefixLen; + $state->mark += 3 + $state->markTail + strlen($regex) - $prefixLen; $state->markTail = 2 + strlen($state->mark); - $rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen).($hasTrailingSlash ? '?' : ''), $state->mark); + $rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen), $state->mark); $code .= "\n .".self::export($rx); $state->regex .= $rx; $vars = array_merge($state->hostVars, $vars); if (!$route->getCondition() && (!is_array($next = $routes[1 + $i] ?? null) || $regex !== $next[1])) { $prevRegex = null; - $state->checkTrailingSlash = $state->checkTrailingSlash || $hasTrailingSlash; $state->default .= sprintf( "%s => array(%s, %s, %s, %s),\n", $state->mark, self::export(array('_route' => $name) + $route->getDefaults()), self::export($vars), - self::export($methods ?: null), - self::export(array_flip($route->getSchemes()) ?: null).($hasTrailingSlash ? ', true' : '') + self::export(array_flip($route->getMethods()) ?: null), + self::export(array_flip($route->getSchemes()) ?: null) ); } else { $prevRegex = $compiledRoute->getRegex(); @@ -485,7 +493,7 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st $state->switch .= <<mark}: -{$combine}{$this->compileRoute($route, $name, $state->supportsRedirections, $hasTrailingSlash, false)} +{$combine}{$this->compileRoute($route, $name, false)} break; EOF; @@ -498,7 +506,7 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st /** * A simple helper to compiles the switch's "default" for both static and dynamic routes. */ - private function compileSwitchDefault(bool $hasVars, string $routesKey, bool $matchHost, bool $supportsRedirections, bool $checkTrailingSlash): string + private function compileSwitchDefault(bool $hasVars, bool $matchHost): string { if ($hasVars) { $code = <<redirect(\$rawPathinfo.'/', \$ret['_route'])); - } - -EOF; - } - if ($supportsRedirections) { + if ($this->supportsRedirections) { $code .= <<getScheme()])) { @@ -550,7 +544,7 @@ private function compileSwitchDefault(bool $hasVars, string $routesKey, bool $ma break; } - return array_replace(\$ret, \$this->redirect(\$rawPathinfo, \$ret['_route'], key(\$requiredSchemes))); + return \$this->redirect(\$rawPathinfo, \$ret['_route'], key(\$requiredSchemes)) + \$ret; } EOF; @@ -574,7 +568,7 @@ private function compileSwitchDefault(bool $hasVars, string $routesKey, bool $ma * * @throws \LogicException */ - private function compileRoute(Route $route, string $name, bool $supportsRedirections, bool $hasTrailingSlash, bool $checkHost): string + private function compileRoute(Route $route, string $name, bool $checkHost): string { $code = ''; $compiledRoute = $route->compile(); @@ -582,12 +576,6 @@ private function compileRoute(Route $route, string $name, bool $supportsRedirect $matches = (bool) $compiledRoute->getPathVariables(); $hostMatches = (bool) $compiledRoute->getHostVariables(); $methods = array_flip($route->getMethods()); - $supportsTrailingSlash = $supportsRedirections && (!$methods || isset($methods['GET'])); - - if ($hasTrailingSlash && !$supportsTrailingSlash) { - $hasTrailingSlash = false; - $conditions[] = "'/' === \$pathinfo[-1]"; - } if ($route->getCondition()) { $expression = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); @@ -625,18 +613,17 @@ private function compileRoute(Route $route, string $name, bool $supportsRedirect // optimize parameters array if ($matches || $hostMatches) { - $vars = array(); - if ($hostMatches && $checkHost) { - $vars[] = '$hostMatches'; - } + $vars = array("array('_route' => '$name')"); if ($matches || ($hostMatches && !$checkHost)) { $vars[] = '$matches'; } - $vars[] = "array('_route' => '$name')"; + if ($hostMatches && $checkHost) { + $vars[] = '$hostMatches'; + } $code .= sprintf( - " \$ret = \$this->mergeDefaults(array_replace(%s), %s);\n", - implode(', ', $vars), + " \$ret = \$this->mergeDefaults(%s, %s);\n", + implode(' + ', $vars), self::export($route->getDefaults()) ); } elseif ($route->getDefaults()) { @@ -645,23 +632,8 @@ private function compileRoute(Route $route, string $name, bool $supportsRedirect $code .= sprintf(" \$ret = array('_route' => '%s');\n", $name); } - if ($hasTrailingSlash) { - $code .= <<redirect(\$rawPathinfo.'/', '$name')); - } - - -EOF; - } - if ($schemes = $route->getSchemes()) { - if (!$supportsRedirections) { + if (!$this->supportsRedirections) { throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); } $schemes = self::export(array_flip($schemes)); @@ -673,7 +645,7 @@ private function compileRoute(Route $route, string $name, bool $supportsRedirect goto $gotoname; } - return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); + return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)) + \$ret; } @@ -694,18 +666,18 @@ private function compileRoute(Route $route, string $name, bool $supportsRedirect EOF; } - if ($hasTrailingSlash || $schemes || $methods) { + if ($schemes || $methods) { $code .= " return \$ret;\n"; } else { $code = substr_replace($code, 'return', $retOffset, 6); } if ($conditions) { $code .= " }\n"; - } elseif ($hasTrailingSlash || $schemes || $methods) { + } elseif ($schemes || $methods) { $code .= ' '; } - if ($hasTrailingSlash || $schemes || $methods) { + if ($schemes || $methods) { $code .= " $gotoname:\n"; } diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php index 014b3c9fdf84a..05315defccdfc 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/StaticPrefixCollection.php @@ -180,12 +180,6 @@ private function getCommonPrefix(string $prefix, string $anotherPrefix): array break; } } - if (1 < $i && '/' === $prefix[$i - 1]) { - --$i; - } - if (null !== $staticLength && 1 < $staticLength && '/' === $prefix[$staticLength - 1]) { - --$staticLength; - } return array(substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i)); } diff --git a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php index 3770a9c24c5e5..33f8642ba7eb4 100644 --- a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php @@ -25,22 +25,21 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable public function match($pathinfo) { try { - $parameters = parent::match($pathinfo); + return parent::match($pathinfo); } catch (ResourceNotFoundException $e) { - if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) { + if ('/' === $pathinfo || !\in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) { throw $e; } try { - $parameters = parent::match($pathinfo.'/'); + $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1); + $ret = parent::match($pathinfo); - return array_replace($parameters, $this->redirect($pathinfo.'/', isset($parameters['_route']) ? $parameters['_route'] : null)); + return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret; } catch (ResourceNotFoundException $e2) { throw $e; } } - - return $parameters; } /** diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php index 8f32dd2770dbe..c61280ac3900b 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php index a998fcc1f3a46..078cf35ff8ac2 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); $host = strtolower($context->getHost()); @@ -82,41 +81,41 @@ public function match($rawPathinfo) .'|/([^/]++)(*:57)' .'|head/([^/]++)(*:77)' .')' - .'|/test/([^/]++)(?' - .'|/(*:103)' + .'|/test/([^/]++)/(?' + .'|(*:103)' .')' .'|/([\']+)(*:119)' - .'|/a(?' - .'|/b\'b/([^/]++)(?' + .'|/a/(?' + .'|b\'b/([^/]++)(?' .'|(*:148)' .'|(*:156)' .')' - .'|/(.*)(*:170)' - .'|/b\'b/([^/]++)(?' - .'|(*:194)' - .'|(*:202)' + .'|(.*)(*:169)' + .'|b\'b/([^/]++)(?' + .'|(*:192)' + .'|(*:200)' .')' .')' - .'|/multi/hello(?:/([^/]++))?(*:238)' + .'|/multi/hello(?:/([^/]++))?(*:236)' .'|/([^/]++)/b/([^/]++)(?' - .'|(*:269)' - .'|(*:277)' + .'|(*:267)' + .'|(*:275)' .')' - .'|/aba/([^/]++)(*:299)' + .'|/aba/([^/]++)(*:297)' .')|(?i:([^\\.]++)\\.example\\.com)(?' .'|/route1(?' - .'|3/([^/]++)(*:359)' - .'|4/([^/]++)(*:377)' + .'|3/([^/]++)(*:357)' + .'|4/([^/]++)(*:375)' .')' .')|(?i:c\\.example\\.com)(?' - .'|/route15/([^/]++)(*:427)' + .'|/route15/([^/]++)(*:425)' .')|[^/]*+(?' - .'|/route16/([^/]++)(*:462)' - .'|/a(?' - .'|/a\\.\\.\\.(*:483)' - .'|/b(?' - .'|/([^/]++)(*:505)' - .'|/c/([^/]++)(*:524)' + .'|/route16/([^/]++)(*:460)' + .'|/a/(?' + .'|a\\.\\.\\.(*:481)' + .'|b/(?' + .'|([^/]++)(*:502)' + .'|c/([^/]++)(*:520)' .')' .')' .')' @@ -130,10 +129,10 @@ public function match($rawPathinfo) $matches = array('foo' => $matches[1] ?? null); // baz4 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array()); + return $this->mergeDefaults(array('_route' => 'baz4') + $matches, array()); // baz5 - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array()); + $ret = $this->mergeDefaults(array('_route' => 'baz5') + $matches, array()); if (!isset(($a = array('POST' => 0))[$requestMethod])) { $allow += $a; goto not_baz5; @@ -143,7 +142,7 @@ public function match($rawPathinfo) not_baz5: // baz.baz6 - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array()); + $ret = $this->mergeDefaults(array('_route' => 'baz.baz6') + $matches, array()); if (!isset(($a = array('PUT' => 0))[$requestMethod])) { $allow += $a; goto not_bazbaz6; @@ -157,7 +156,7 @@ public function match($rawPathinfo) $matches = array('foo' => $matches[1] ?? null); // foo1 - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array()); + $ret = $this->mergeDefaults(array('_route' => 'foo1') + $matches, array()); if (!isset(($a = array('PUT' => 0))[$requestMethod])) { $allow += $a; goto not_foo1; @@ -167,18 +166,18 @@ public function match($rawPathinfo) not_foo1: break; - case 194: + case 192: $matches = array('foo1' => $matches[1] ?? null); // foo2 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array()); + return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array()); break; - case 269: + case 267: $matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null); // foo3 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array()); + return $this->mergeDefaults(array('_route' => 'foo3') + $matches, array()); break; default: @@ -188,18 +187,18 @@ public function match($rawPathinfo) 77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null), 119 => array(array('_route' => 'quoter'), array('quoter'), null, null), 156 => array(array('_route' => 'bar1'), array('bar'), null, null), - 170 => array(array('_route' => 'overridden'), array('var'), null, null), - 202 => array(array('_route' => 'bar2'), array('bar1'), null, null), - 238 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), - 277 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), - 299 => array(array('_route' => 'foo4'), array('foo'), null, null), - 359 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), - 377 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), - 427 => array(array('_route' => 'route15'), array('name'), null, null), - 462 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), - 483 => array(array('_route' => 'a'), array(), null, null), - 505 => array(array('_route' => 'b'), array('var'), null, null), - 524 => array(array('_route' => 'c'), array('var'), null, null), + 169 => array(array('_route' => 'overridden'), array('var'), null, null), + 200 => array(array('_route' => 'bar2'), array('bar1'), null, null), + 236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), + 275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), + 297 => array(array('_route' => 'foo4'), array('foo'), null, null), + 357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), + 375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), + 425 => array(array('_route' => 'route15'), array('name'), null, null), + 460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), + 481 => array(array('_route' => 'a'), array(), null, null), + 502 => array(array('_route' => 'b'), array('var'), null, null), + 520 => array(array('_route' => 'c'), array('var'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -218,7 +217,7 @@ public function match($rawPathinfo) return $ret; } - if (524 === $m) { + if (520 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php index affd07d3d45ce..425bdf34bca0c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php index a9902498a1348..edb4c74259e5c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php @@ -15,11 +15,26 @@ public function __construct(RequestContext $context) $this->context = $context; } - public function match($rawPathinfo) + public function match($pathinfo) + { + $allow = array(); + if ($ret = $this->doMatch($pathinfo, $allow)) { + return $ret; + } + if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) { + $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1); + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + } + + throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + } + + private function doMatch(string $rawPathinfo, array &$allow = array()): ?array { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); @@ -30,32 +45,34 @@ public function match($rawPathinfo) $matchedPathinfo = $pathinfo; $regexList = array( 0 => '{^(?' - .'|/(en|fr)(?' - .'|/admin/post(?' - .'|/?(*:34)' - .'|/new(*:45)' - .'|/(\\d+)(?' - .'|(*:61)' - .'|/edit(*:73)' - .'|/delete(*:87)' + .'|/(en|fr)/(?' + .'|admin/post/(?' + .'|(*:33)' + .'|new(*:43)' + .'|(\\d+)(?' + .'|(*:58)' + .'|/(?' + .'|edit(*:73)' + .'|delete(*:86)' + .')' .')' .')' - .'|/blog(?' - .'|/?(*:106)' - .'|/rss\\.xml(*:123)' - .'|/p(?' - .'|age/([^/]++)(*:148)' - .'|osts/([^/]++)(*:169)' + .'|blog/(?' + .'|(*:104)' + .'|rss\\.xml(*:120)' + .'|p(?' + .'|age/([^/]++)(*:144)' + .'|osts/([^/]++)(*:165)' .')' - .'|/comments/(\\d+)/new(*:197)' - .'|/search(*:212)' + .'|comments/(\\d+)/new(*:192)' + .'|search(*:206)' .')' - .'|/log(?' - .'|in(*:230)' - .'|out(*:241)' + .'|log(?' + .'|in(*:223)' + .'|out(*:234)' .')' .')' - .'|/(en|fr)?(*:260)' + .'|/(en|fr)?(*:253)' .')$}sD', ); @@ -64,20 +81,20 @@ public function match($rawPathinfo) switch ($m = (int) $matches['MARK']) { default: $routes = array( - 34 => array(array('_route' => 'a', '_locale' => 'en'), array('_locale'), null, null, true), - 45 => array(array('_route' => 'b', '_locale' => 'en'), array('_locale'), null, null), - 61 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null), + 33 => array(array('_route' => 'a', '_locale' => 'en'), array('_locale'), null, null), + 43 => array(array('_route' => 'b', '_locale' => 'en'), array('_locale'), null, null), + 58 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null), 73 => array(array('_route' => 'd', '_locale' => 'en'), array('_locale', 'id'), null, null), - 87 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null), - 106 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null, true), - 123 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null), - 148 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null), - 169 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null), - 197 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null), - 212 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null), - 230 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null), - 241 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null), - 260 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null), + 86 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null), + 104 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null), + 120 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null), + 144 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null), + 165 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null), + 192 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null), + 206 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null), + 223 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null), + 234 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null), + 253 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -88,22 +105,13 @@ public function match($rawPathinfo) } } - if (empty($routes[$m][4]) || '/' === $pathinfo[-1]) { - // no-op - } elseif ('GET' !== $canonicalMethod) { - $allow['GET'] = 'GET'; - break; - } else { - return array_replace($ret, $this->redirect($rawPathinfo.'/', $ret['_route'])); - } - if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { if ('GET' !== $canonicalMethod) { $allow['GET'] = 'GET'; break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -114,7 +122,7 @@ public function match($rawPathinfo) return $ret; } - if (260 === $m) { + if (253 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); @@ -122,6 +130,6 @@ public function match($rawPathinfo) } } - throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + return null; } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php index 5850cbb07685c..903b937b9e7a8 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); @@ -30,19 +29,19 @@ public function match($rawPathinfo) $matchedPathinfo = $pathinfo; $regexList = array( 0 => '{^(?' - .'|/abc([^/]++)(?' - .'|/1(?' + .'|/abc([^/]++)/(?' + .'|1(?' .'|(*:27)' .'|0(?' .'|(*:38)' .'|0(*:46)' .')' .')' - .'|/2(?' - .'|(*:60)' + .'|2(?' + .'|(*:59)' .'|0(?' - .'|(*:71)' - .'|0(*:79)' + .'|(*:70)' + .'|0(*:78)' .')' .')' .')' @@ -57,9 +56,9 @@ public function match($rawPathinfo) 27 => array(array('_route' => 'r1'), array('foo'), null, null), 38 => array(array('_route' => 'r10'), array('foo'), null, null), 46 => array(array('_route' => 'r100'), array('foo'), null, null), - 60 => array(array('_route' => 'r2'), array('foo'), null, null), - 71 => array(array('_route' => 'r20'), array('foo'), null, null), - 79 => array(array('_route' => 'r200'), array('foo'), null, null), + 59 => array(array('_route' => 'r2'), array('foo'), null, null), + 70 => array(array('_route' => 'r20'), array('foo'), null, null), + 78 => array(array('_route' => 'r200'), array('foo'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -78,7 +77,7 @@ public function match($rawPathinfo) return $ret; } - if (79 === $m) { + if (78 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php index 2973d7eee785c..595ced7592313 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); $host = strtolower($context->getHost()); @@ -46,10 +45,10 @@ public function match($rawPathinfo) $matches = array('foo' => $matches[1] ?? null, 'foo' => $matches[2] ?? null); // r1 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'r1')), array()); + return $this->mergeDefaults(array('_route' => 'r1') + $matches, array()); // r2 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'r2')), array()); + return $this->mergeDefaults(array('_route' => 'r2') + $matches, array()); break; } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php index 9e204494f7d7c..291114676ca0b 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -15,11 +15,26 @@ public function __construct(RequestContext $context) $this->context = $context; } - public function match($rawPathinfo) + public function match($pathinfo) + { + $allow = array(); + if ($ret = $this->doMatch($pathinfo, $allow)) { + return $ret; + } + if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) { + $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1); + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + } + + throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + } + + private function doMatch(string $rawPathinfo, array &$allow = array()): ?array { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); $host = strtolower($context->getHost()); @@ -28,16 +43,16 @@ public function match($rawPathinfo) $canonicalMethod = 'GET'; } - switch ($trimmedPathinfo) { + switch ($pathinfo) { default: $routes = array( '/test/baz' => array(array('_route' => 'baz'), null, null, null), '/test/baz.html' => array(array('_route' => 'baz2'), null, null, null), - '/test/baz3' => array(array('_route' => 'baz3'), null, null, null, true), + '/test/baz3/' => array(array('_route' => 'baz3'), null, null, null), '/foofoo' => array(array('_route' => 'foofoo', 'def' => 'test'), null, null, null), '/spa ce' => array(array('_route' => 'space'), null, null, null), '/multi/new' => array(array('_route' => 'overridden2'), null, null, null), - '/multi/hey' => array(array('_route' => 'hey'), null, null, null, true), + '/multi/hey/' => array(array('_route' => 'hey'), null, null, null), '/ababa' => array(array('_route' => 'ababa'), null, null, null), '/route1' => array(array('_route' => 'route1'), 'a.example.com', null, null), '/c2/route2' => array(array('_route' => 'route2'), 'a.example.com', null, null), @@ -52,10 +67,10 @@ public function match($rawPathinfo) '/nonsecure' => array(array('_route' => 'nonsecure'), null, null, array('http' => 0)), ); - if (!isset($routes[$trimmedPathinfo])) { + if (!isset($routes[$pathinfo])) { break; } - list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$trimmedPathinfo]; + list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo]; if ($requiredHost) { if ('#' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) { @@ -67,22 +82,13 @@ public function match($rawPathinfo) } } - if (empty($routes[$trimmedPathinfo][4]) || '/' === $pathinfo[-1]) { - // no-op - } elseif ('GET' !== $canonicalMethod) { - $allow['GET'] = 'GET'; - break; - } else { - return array_replace($ret, $this->redirect($rawPathinfo.'/', $ret['_route'])); - } - if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { if ('GET' !== $canonicalMethod) { $allow['GET'] = 'GET'; break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -102,41 +108,41 @@ public function match($rawPathinfo) .'|/([^/]++)(*:57)' .'|head/([^/]++)(*:77)' .')' - .'|/test/([^/]++)(?' - .'|/?(*:104)' + .'|/test/([^/]++)/(?' + .'|(*:103)' .')' - .'|/([\']+)(*:120)' - .'|/a(?' - .'|/b\'b/([^/]++)(?' - .'|(*:149)' - .'|(*:157)' + .'|/([\']+)(*:119)' + .'|/a/(?' + .'|b\'b/([^/]++)(?' + .'|(*:148)' + .'|(*:156)' .')' - .'|/(.*)(*:171)' - .'|/b\'b/([^/]++)(?' - .'|(*:195)' - .'|(*:203)' + .'|(.*)(*:169)' + .'|b\'b/([^/]++)(?' + .'|(*:192)' + .'|(*:200)' .')' .')' - .'|/multi/hello(?:/([^/]++))?(*:239)' + .'|/multi/hello(?:/([^/]++))?(*:236)' .'|/([^/]++)/b/([^/]++)(?' - .'|(*:270)' - .'|(*:278)' + .'|(*:267)' + .'|(*:275)' .')' - .'|/aba/([^/]++)(*:300)' + .'|/aba/([^/]++)(*:297)' .')|(?i:([^\\.]++)\\.example\\.com)(?' .'|/route1(?' - .'|3/([^/]++)(*:360)' - .'|4/([^/]++)(*:378)' + .'|3/([^/]++)(*:357)' + .'|4/([^/]++)(*:375)' .')' .')|(?i:c\\.example\\.com)(?' - .'|/route15/([^/]++)(*:428)' + .'|/route15/([^/]++)(*:425)' .')|[^/]*+(?' - .'|/route16/([^/]++)(*:463)' - .'|/a(?' - .'|/a\\.\\.\\.(*:484)' - .'|/b(?' - .'|/([^/]++)(*:506)' - .'|/c/([^/]++)(*:525)' + .'|/route16/([^/]++)(*:460)' + .'|/a/(?' + .'|a\\.\\.\\.(*:481)' + .'|b/(?' + .'|([^/]++)(*:502)' + .'|c/([^/]++)(*:520)' .')' .')' .')' @@ -146,53 +152,38 @@ public function match($rawPathinfo) foreach ($regexList as $offset => $regex) { while (preg_match($regex, $matchedPathinfo, $matches)) { switch ($m = (int) $matches['MARK']) { - case 104: + case 103: $matches = array('foo' => $matches[1] ?? null); // baz4 - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array()); - if ('/' === $pathinfo[-1]) { - // no-op - } elseif ('GET' !== $canonicalMethod) { - $allow['GET'] = 'GET'; - goto not_baz4; - } else { - return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz4')); - } - - return $ret; - not_baz4: + return $this->mergeDefaults(array('_route' => 'baz4') + $matches, array()); // baz5 - if ('/' === $pathinfo[-1]) { - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array()); - if (!isset(($a = array('POST' => 0))[$requestMethod])) { - $allow += $a; - goto not_baz5; - } - - return $ret; + $ret = $this->mergeDefaults(array('_route' => 'baz5') + $matches, array()); + if (!isset(($a = array('POST' => 0))[$requestMethod])) { + $allow += $a; + goto not_baz5; } + + return $ret; not_baz5: // baz.baz6 - if ('/' === $pathinfo[-1]) { - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array()); - if (!isset(($a = array('PUT' => 0))[$requestMethod])) { - $allow += $a; - goto not_bazbaz6; - } - - return $ret; + $ret = $this->mergeDefaults(array('_route' => 'baz.baz6') + $matches, array()); + if (!isset(($a = array('PUT' => 0))[$requestMethod])) { + $allow += $a; + goto not_bazbaz6; } + + return $ret; not_bazbaz6: break; - case 149: + case 148: $matches = array('foo' => $matches[1] ?? null); // foo1 - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array()); + $ret = $this->mergeDefaults(array('_route' => 'foo1') + $matches, array()); if (!isset(($a = array('PUT' => 0))[$requestMethod])) { $allow += $a; goto not_foo1; @@ -202,18 +193,18 @@ public function match($rawPathinfo) not_foo1: break; - case 195: + case 192: $matches = array('foo1' => $matches[1] ?? null); // foo2 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array()); + return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array()); break; - case 270: + case 267: $matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null); // foo3 - return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array()); + return $this->mergeDefaults(array('_route' => 'foo3') + $matches, array()); break; default: @@ -221,20 +212,20 @@ public function match($rawPathinfo) 34 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null), 57 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null), 77 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null), - 120 => array(array('_route' => 'quoter'), array('quoter'), null, null), - 157 => array(array('_route' => 'bar1'), array('bar'), null, null), - 171 => array(array('_route' => 'overridden'), array('var'), null, null), - 203 => array(array('_route' => 'bar2'), array('bar1'), null, null), - 239 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), - 278 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), - 300 => array(array('_route' => 'foo4'), array('foo'), null, null), - 360 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), - 378 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), - 428 => array(array('_route' => 'route15'), array('name'), null, null), - 463 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), - 484 => array(array('_route' => 'a'), array(), null, null), - 506 => array(array('_route' => 'b'), array('var'), null, null), - 525 => array(array('_route' => 'c'), array('var'), null, null), + 119 => array(array('_route' => 'quoter'), array('quoter'), null, null), + 156 => array(array('_route' => 'bar1'), array('bar'), null, null), + 169 => array(array('_route' => 'overridden'), array('var'), null, null), + 200 => array(array('_route' => 'bar2'), array('bar1'), null, null), + 236 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null), + 275 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null), + 297 => array(array('_route' => 'foo4'), array('foo'), null, null), + 357 => array(array('_route' => 'route13'), array('var1', 'name'), null, null), + 375 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null), + 425 => array(array('_route' => 'route15'), array('name'), null, null), + 460 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null), + 481 => array(array('_route' => 'a'), array(), null, null), + 502 => array(array('_route' => 'b'), array('var'), null, null), + 520 => array(array('_route' => 'c'), array('var'), null, null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -251,7 +242,7 @@ public function match($rawPathinfo) break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -262,7 +253,7 @@ public function match($rawPathinfo) return $ret; } - if (525 === $m) { + if (520 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); @@ -270,6 +261,6 @@ public function match($rawPathinfo) } } - throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + return null; } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php index e49293f03c8ec..97cfa9bd72120 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php index 17a6a7e421a32..f65be557062a3 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php index 97aba359841e2..581d95861010a 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php @@ -15,11 +15,26 @@ public function __construct(RequestContext $context) $this->context = $context; } - public function match($rawPathinfo) + public function match($pathinfo) + { + $allow = array(); + if ($ret = $this->doMatch($pathinfo, $allow)) { + return $ret; + } + if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) { + $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1); + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + } + + throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + } + + private function doMatch(string $rawPathinfo, array &$allow = array()): ?array { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); @@ -27,36 +42,27 @@ public function match($rawPathinfo) $canonicalMethod = 'GET'; } - switch ($trimmedPathinfo) { + switch ($pathinfo) { default: $routes = array( '/a/11' => array(array('_route' => 'a_first'), null, null, null), '/a/22' => array(array('_route' => 'a_second'), null, null, null), '/a/333' => array(array('_route' => 'a_third'), null, null, null), - '/a/44' => array(array('_route' => 'a_fourth'), null, null, null, true), - '/a/55' => array(array('_route' => 'a_fifth'), null, null, null, true), - '/a/66' => array(array('_route' => 'a_sixth'), null, null, null, true), - '/nested/group/a' => array(array('_route' => 'nested_a'), null, null, null, true), - '/nested/group/b' => array(array('_route' => 'nested_b'), null, null, null, true), - '/nested/group/c' => array(array('_route' => 'nested_c'), null, null, null, true), - '/slashed/group' => array(array('_route' => 'slashed_a'), null, null, null, true), - '/slashed/group/b' => array(array('_route' => 'slashed_b'), null, null, null, true), - '/slashed/group/c' => array(array('_route' => 'slashed_c'), null, null, null, true), + '/a/44/' => array(array('_route' => 'a_fourth'), null, null, null), + '/a/55/' => array(array('_route' => 'a_fifth'), null, null, null), + '/a/66/' => array(array('_route' => 'a_sixth'), null, null, null), + '/nested/group/a/' => array(array('_route' => 'nested_a'), null, null, null), + '/nested/group/b/' => array(array('_route' => 'nested_b'), null, null, null), + '/nested/group/c/' => array(array('_route' => 'nested_c'), null, null, null), + '/slashed/group/' => array(array('_route' => 'slashed_a'), null, null, null), + '/slashed/group/b/' => array(array('_route' => 'slashed_b'), null, null, null), + '/slashed/group/c/' => array(array('_route' => 'slashed_c'), null, null, null), ); - if (!isset($routes[$trimmedPathinfo])) { - break; - } - list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$trimmedPathinfo]; - - if (empty($routes[$trimmedPathinfo][4]) || '/' === $pathinfo[-1]) { - // no-op - } elseif ('GET' !== $canonicalMethod) { - $allow['GET'] = 'GET'; + if (!isset($routes[$pathinfo])) { break; - } else { - return array_replace($ret, $this->redirect($rawPathinfo.'/', $ret['_route'])); } + list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo]; if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { if ('GET' !== $canonicalMethod) { @@ -64,7 +70,7 @@ public function match($rawPathinfo) break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -106,7 +112,7 @@ public function match($rawPathinfo) break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -125,6 +131,6 @@ public function match($rawPathinfo) } } - throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + return null; } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php index 27804d575dc43..5b07bd949df6d 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); @@ -56,17 +55,17 @@ public function match($rawPathinfo) $matchedPathinfo = $pathinfo; $regexList = array( 0 => '{^(?' - .'|/trailing/regex(?' - .'|/no\\-methods/([^/]++)/(*:47)' - .'|/get\\-method/([^/]++)/(*:76)' - .'|/head\\-method/([^/]++)/(*:106)' - .'|/post\\-method/([^/]++)/(*:137)' + .'|/trailing/regex/(?' + .'|no\\-methods/([^/]++)/(*:47)' + .'|get\\-method/([^/]++)/(*:75)' + .'|head\\-method/([^/]++)/(*:104)' + .'|post\\-method/([^/]++)/(*:134)' .')' - .'|/not\\-trailing/regex(?' - .'|/no\\-methods/([^/]++)(*:190)' - .'|/get\\-method/([^/]++)(*:219)' - .'|/head\\-method/([^/]++)(*:249)' - .'|/post\\-method/([^/]++)(*:279)' + .'|/not\\-trailing/regex/(?' + .'|no\\-methods/([^/]++)(*:187)' + .'|get\\-method/([^/]++)(*:215)' + .'|head\\-method/([^/]++)(*:244)' + .'|post\\-method/([^/]++)(*:273)' .')' .')$}sD', ); @@ -77,13 +76,13 @@ public function match($rawPathinfo) default: $routes = array( 47 => array(array('_route' => 'regex_trailing_slash_no_methods'), array('param'), null, null), - 76 => array(array('_route' => 'regex_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), - 106 => array(array('_route' => 'regex_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), - 137 => array(array('_route' => 'regex_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), - 190 => array(array('_route' => 'regex_not_trailing_slash_no_methods'), array('param'), null, null), - 219 => array(array('_route' => 'regex_not_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), - 249 => array(array('_route' => 'regex_not_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), - 279 => array(array('_route' => 'regex_not_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), + 75 => array(array('_route' => 'regex_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), + 104 => array(array('_route' => 'regex_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), + 134 => array(array('_route' => 'regex_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), + 187 => array(array('_route' => 'regex_not_trailing_slash_no_methods'), array('param'), null, null), + 215 => array(array('_route' => 'regex_not_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), + 244 => array(array('_route' => 'regex_not_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), + 273 => array(array('_route' => 'regex_not_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -102,7 +101,7 @@ public function match($rawPathinfo) return $ret; } - if (279 === $m) { + if (273 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php index 6046e172321db..7ec20297daa18 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php @@ -15,11 +15,26 @@ public function __construct(RequestContext $context) $this->context = $context; } - public function match($rawPathinfo) + public function match($pathinfo) + { + $allow = array(); + if ($ret = $this->doMatch($pathinfo, $allow)) { + return $ret; + } + if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) { + $pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1); + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + } + + throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + } + + private function doMatch(string $rawPathinfo, array &$allow = array()): ?array { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); @@ -27,32 +42,23 @@ public function match($rawPathinfo) $canonicalMethod = 'GET'; } - switch ($trimmedPathinfo) { + switch ($pathinfo) { default: $routes = array( - '/trailing/simple/no-methods' => array(array('_route' => 'simple_trailing_slash_no_methods'), null, null, null, true), - '/trailing/simple/get-method' => array(array('_route' => 'simple_trailing_slash_GET_method'), null, array('GET' => 0), null, true), - '/trailing/simple/head-method' => array(array('_route' => 'simple_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null, true), - '/trailing/simple/post-method' => array(array('_route' => 'simple_trailing_slash_POST_method'), null, array('POST' => 0), null, true), + '/trailing/simple/no-methods/' => array(array('_route' => 'simple_trailing_slash_no_methods'), null, null, null), + '/trailing/simple/get-method/' => array(array('_route' => 'simple_trailing_slash_GET_method'), null, array('GET' => 0), null), + '/trailing/simple/head-method/' => array(array('_route' => 'simple_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null), + '/trailing/simple/post-method/' => array(array('_route' => 'simple_trailing_slash_POST_method'), null, array('POST' => 0), null), '/not-trailing/simple/no-methods' => array(array('_route' => 'simple_not_trailing_slash_no_methods'), null, null, null), '/not-trailing/simple/get-method' => array(array('_route' => 'simple_not_trailing_slash_GET_method'), null, array('GET' => 0), null), '/not-trailing/simple/head-method' => array(array('_route' => 'simple_not_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null), '/not-trailing/simple/post-method' => array(array('_route' => 'simple_not_trailing_slash_POST_method'), null, array('POST' => 0), null), ); - if (!isset($routes[$trimmedPathinfo])) { - break; - } - list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$trimmedPathinfo]; - - if (empty($routes[$trimmedPathinfo][4]) || '/' === $pathinfo[-1]) { - // no-op - } elseif ('GET' !== $canonicalMethod) { - $allow['GET'] = 'GET'; + if (!isset($routes[$pathinfo])) { break; - } else { - return array_replace($ret, $this->redirect($rawPathinfo.'/', $ret['_route'])); } + list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo]; if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { if ('GET' !== $canonicalMethod) { @@ -60,7 +66,7 @@ public function match($rawPathinfo) break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -74,17 +80,17 @@ public function match($rawPathinfo) $matchedPathinfo = $pathinfo; $regexList = array( 0 => '{^(?' - .'|/trailing/regex(?' - .'|/no\\-methods/([^/]++)/?(*:48)' - .'|/get\\-method/([^/]++)/?(*:78)' - .'|/head\\-method/([^/]++)/(*:108)' - .'|/post\\-method/([^/]++)/(*:139)' + .'|/trailing/regex/(?' + .'|no\\-methods/([^/]++)/(*:47)' + .'|get\\-method/([^/]++)/(*:75)' + .'|head\\-method/([^/]++)/(*:104)' + .'|post\\-method/([^/]++)/(*:134)' .')' - .'|/not\\-trailing/regex(?' - .'|/no\\-methods/([^/]++)(*:192)' - .'|/get\\-method/([^/]++)(*:221)' - .'|/head\\-method/([^/]++)(*:251)' - .'|/post\\-method/([^/]++)(*:281)' + .'|/not\\-trailing/regex/(?' + .'|no\\-methods/([^/]++)(*:187)' + .'|get\\-method/([^/]++)(*:215)' + .'|head\\-method/([^/]++)(*:244)' + .'|post\\-method/([^/]++)(*:273)' .')' .')$}sD', ); @@ -94,14 +100,14 @@ public function match($rawPathinfo) switch ($m = (int) $matches['MARK']) { default: $routes = array( - 48 => array(array('_route' => 'regex_trailing_slash_no_methods'), array('param'), null, null, true), - 78 => array(array('_route' => 'regex_trailing_slash_GET_method'), array('param'), array('GET' => 0), null, true), - 108 => array(array('_route' => 'regex_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), - 139 => array(array('_route' => 'regex_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), - 192 => array(array('_route' => 'regex_not_trailing_slash_no_methods'), array('param'), null, null), - 221 => array(array('_route' => 'regex_not_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), - 251 => array(array('_route' => 'regex_not_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), - 281 => array(array('_route' => 'regex_not_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), + 47 => array(array('_route' => 'regex_trailing_slash_no_methods'), array('param'), null, null), + 75 => array(array('_route' => 'regex_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), + 104 => array(array('_route' => 'regex_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), + 134 => array(array('_route' => 'regex_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), + 187 => array(array('_route' => 'regex_not_trailing_slash_no_methods'), array('param'), null, null), + 215 => array(array('_route' => 'regex_not_trailing_slash_GET_method'), array('param'), array('GET' => 0), null), + 244 => array(array('_route' => 'regex_not_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null), + 273 => array(array('_route' => 'regex_not_trailing_slash_POST_method'), array('param'), array('POST' => 0), null), ); list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m]; @@ -112,22 +118,13 @@ public function match($rawPathinfo) } } - if (empty($routes[$m][4]) || '/' === $pathinfo[-1]) { - // no-op - } elseif ('GET' !== $canonicalMethod) { - $allow['GET'] = 'GET'; - break; - } else { - return array_replace($ret, $this->redirect($rawPathinfo.'/', $ret['_route'])); - } - if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { if ('GET' !== $canonicalMethod) { $allow['GET'] = 'GET'; break; } - return array_replace($ret, $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes))); + return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret; } if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { @@ -138,7 +135,7 @@ public function match($rawPathinfo) return $ret; } - if (281 === $m) { + if (273 === $m) { break; } $regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m)); @@ -146,6 +143,6 @@ public function match($rawPathinfo) } } - throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException(); + return null; } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php index df7bfb029fe1a..925e340368b9c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php index e1af79de23eab..2e295b29023ba 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php @@ -19,7 +19,6 @@ public function match($rawPathinfo) { $allow = array(); $pathinfo = rawurldecode($rawPathinfo); - $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; $requestMethod = $canonicalMethod = $context->getMethod(); $host = strtolower($context->getHost()); @@ -32,11 +31,11 @@ public function match($rawPathinfo) case '/': // a if (preg_match('#^(?P[^\\.]++)\\.e\\.c\\.b\\.a$#sDi', $host, $hostMatches)) { - return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'a')), array()); + return $this->mergeDefaults(array('_route' => 'a') + $hostMatches, array()); } // c if (preg_match('#^(?P[^\\.]++)\\.e\\.c\\.b\\.a$#sDi', $host, $hostMatches)) { - return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'c')), array()); + return $this->mergeDefaults(array('_route' => 'c') + $hostMatches, array()); } // b if ('d.c.b.a' === $host) { diff --git a/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php index 7ea8aa2834167..28f65aeeb5aa7 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php @@ -19,14 +19,6 @@ class DumpedRedirectableUrlMatcherTest extends RedirectableUrlMatcherTest { - /** - * @expectedException \Symfony\Component\Routing\Exception\MethodNotAllowedException - */ - public function testRedirectWhenNoSlashForNonSafeMethod() - { - parent::testRedirectWhenNoSlashForNonSafeMethod(); - } - protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) { static $i = 0; diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php index e23344a8156bf..24e12ac177eb2 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php @@ -48,7 +48,7 @@ public function routeProvider() ), << prefix_segment -> leading_segment EOF @@ -61,7 +61,7 @@ public function routeProvider() ), << prefix_segment -> leading_segment EOF @@ -75,7 +75,7 @@ public function routeProvider() ), << nested_segment -> some_segment -> other_segment @@ -92,12 +92,12 @@ public function routeProvider() array('/group/ff/', 'ff'), ), << aa -> bb -> cc root -/group +/group/ -> dd -> ee -> ff @@ -118,17 +118,17 @@ public function routeProvider() array('/aaa/333/', 'third_aaa'), ), << first_aaa -> second_aaa -> third_aaa -/prefixed --> /prefixed/group +/prefixed/ +-> /prefixed/group/ -> -> aa -> -> bb -> -> cc -> root --> /prefixed/group +-> /prefixed/group/ -> -> dd -> -> ee -> -> ff diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php index 5f226271bf85c..1cbfcfd0262b0 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -27,6 +27,16 @@ public function testRedirectWhenNoSlash() $matcher->match('/foo'); } + public function testRedirectWhenSlash() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo')); + + $matcher = $this->getUrlMatcher($coll); + $matcher->expects($this->once())->method('redirect')->will($this->returnValue(array())); + $matcher->match('/foo/'); + } + /** * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException */