From a6ad331c14d3fd91594aed9b909c0c65ea7cae05 Mon Sep 17 00:00:00 2001 From: Konstantin Tjuterev Date: Tue, 31 Jul 2012 19:44:28 +0300 Subject: [PATCH] [Routing] fixed handling of one or more optional route parameters, followed by suffix --- .../Component/Routing/Matcher/UrlMatcher.php | 3 ++- .../Component/Routing/RouteCompiler.php | 10 ++++---- .../Routing/Matcher/UrlMatcherTest.php | 24 +++++++++++++++++++ .../Component/Routing/RouteCompilerTest.php | 19 ++++++++++++++- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 5ff8070ea0cc7..79c743b9fab40 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -141,7 +141,8 @@ protected function mergeDefaults($params, $defaults) { $parameters = $defaults; foreach ($params as $key => $value) { - if (!is_int($key)) { + // empty value in matches ($params) means not-matched optional parameter + if (!is_int($key) && !empty($value)) { $parameters[$key] = rawurldecode($value); } } diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 72ececc5caa38..76bc284c23051 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -69,7 +69,10 @@ public function compile(Route $route) $token = $tokens[$i]; if ('variable' === $token[0] && $route->hasDefault($token[3])) { $firstOptional = $i; + } elseif ('text' === $token[0]) { + continue; } else { + // the case for variable w/o default value break; } } @@ -110,15 +113,10 @@ private function computeRegexp(array $tokens, $index, $firstOptional) // When the only token is an optional variable token, the separator is required return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], '#'), $token[3], $token[2]); } else { - $nbTokens = count($tokens); $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], '#'), $token[3], $token[2]); if ($index >= $firstOptional) { // Enclose each optional tokens in a subpattern to make it optional - $regexp = "(?:$regexp"; - if ($nbTokens - 1 == $index) { - // Close the optional subpatterns - $regexp .= str_repeat(")?", $nbTokens - $firstOptional); - } + $regexp = "(?:$regexp)?"; } return $regexp; diff --git a/tests/Symfony/Tests/Component/Routing/Matcher/UrlMatcherTest.php b/tests/Symfony/Tests/Component/Routing/Matcher/UrlMatcherTest.php index 8acff1686d7b3..c88af86dc4533 100644 --- a/tests/Symfony/Tests/Component/Routing/Matcher/UrlMatcherTest.php +++ b/tests/Symfony/Tests/Component/Routing/Matcher/UrlMatcherTest.php @@ -118,6 +118,30 @@ public function testMatch() $matcher = new UrlMatcher($collection, new RequestContext(), array()); $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo')); $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/')); + + // Route with 1 optional variable and text suffix at the end + $collection = new RouteCollection(); + $collection->add('bar', new Route('/bar-{foo}.html', array('foo' => 'foo'), array('foo' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext(), array()); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo'), $matcher->match('/bar.html')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo'), $matcher->match('/bar-foo.html')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'bar'), $matcher->match('/bar-bar.html')); + + // Route with 2 optional variables and text suffix at the end + $collection = new RouteCollection(); + $collection->add( + 'bar', + new Route( + '/bar-{foo}-{baz}.html', + array('foo' => 'foo', 'baz' => '1'), + array('foo' => 'foo|bar', 'baz' => '\d') + ) + ); + $matcher = new UrlMatcher($collection, new RequestContext(), array()); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'baz' => 1), $matcher->match('/bar.html')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'baz' => 1), $matcher->match('/bar-foo.html')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'baz' => 2), $matcher->match('/bar-foo-2.html')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'baz' => 2), $matcher->match('/bar-2.html')); } public function testMatchWithPrefixes() diff --git a/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php b/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php index 81c6a93a10b73..e9d2809796aeb 100644 --- a/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php +++ b/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php @@ -68,7 +68,7 @@ public function provideCompileData() array( 'Route with several variables that have default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), - '/foo', '#^/foo(?:/(?P[^/]+?)(?:/(?P[^/]+?))?)?$#s', array('bar', 'foobar'), array( + '/foo', '#^/foo(?:/(?P[^/]+?))?(?:/(?P[^/]+?))?$#s', array('bar', 'foobar'), array( array('variable', '/', '[^/]+?', 'foobar'), array('variable', '/', '[^/]+?', 'bar'), array('text', '/foo'), @@ -96,6 +96,23 @@ public function provideCompileData() '', '#^/(?P(foo|bar))?$#s', array('bar'), array( array('variable', '/', '(foo|bar)', 'bar'), )), + array( + 'Route with optional variable and suffix', + array('/foo-{bar}.html', array('bar' => 'bar'), array('bar' => '(foo|bar)')), + '/foo', '#^/foo(?:\-(?P(foo|bar)))?\.html$#s', array('bar'), array( + array('text', '.html'), + array('variable', '-', '(foo|bar)', 'bar'), + array('text', '/foo'), + )), + array( + 'Route with several optional variables and suffix', + array('/foo-{bar}-{baz}.html', array('bar' => 'bar', 'baz' => 'baz'), array('bar' => '(foo|bar)', 'baz' => '(baz|zab)')), + '/foo', '#^/foo(?:\-(?P(foo|bar)))?(?:\-(?P(baz|zab)))?\.html$#s', array('bar', 'baz'), array( + array('text', '.html'), + array('variable', '-', '(baz|zab)', 'baz'), + array('variable', '-', '(foo|bar)', 'bar'), + array('text', '/foo'), + )), ); }