diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index dc5e5e62c6..5d35663571 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -26,6 +26,8 @@ class RouteCompiler implements RouteCompilerInterface * @param Route $route A Route instance * * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If a variable is referenced more than once */ public function compile(Route $route) { @@ -34,22 +36,22 @@ class RouteCompiler implements RouteCompilerInterface $tokens = array(); $variables = array(); $pos = 0; - preg_match_all('#.\{([\w\d_]+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + preg_match_all('#.\{(\w+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); foreach ($matches as $match) { if ($text = substr($pattern, $pos, $match[0][1] - $pos)) { $tokens[] = array('text', $text); } - $seps = array($pattern[$pos]); + $pos = $match[0][1] + strlen($match[0][0]); $var = $match[1][0]; if ($req = $route->getRequirement($var)) { $regexp = $req; } else { - if ($pos !== $len) { - $seps[] = $pattern[$pos]; - } - $regexp = sprintf('[^%s]+?', preg_quote(implode('', array_unique($seps)), self::REGEX_DELIMITER)); + // Use the character following the variable as the separator when available + // Use the character preceding the variable otherwise + $separator = $pos !== $len ? $pattern[$pos] : $match[0][0][0]; + $regexp = sprintf('[^%s]+', preg_quote($separator, self::REGEX_DELIMITER)); } $tokens[] = array('variable', $match[0][0][0], $regexp, $var); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache index a937998089..25f6060fdd 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache @@ -7,17 +7,17 @@ RewriteCond %{REQUEST_URI} ^/foo/(baz|symfony)$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foo,E=_ROUTING_bar:%1,E=_ROUTING_def:test] # bar -RewriteCond %{REQUEST_URI} ^/bar/([^/]+?)$ +RewriteCond %{REQUEST_URI} ^/bar/([^/]+)$ RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] RewriteRule .* - [S=1,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_HEAD:1] -RewriteCond %{REQUEST_URI} ^/bar/([^/]+?)$ +RewriteCond %{REQUEST_URI} ^/bar/([^/]+)$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:bar,E=_ROUTING_foo:%1] # baragain -RewriteCond %{REQUEST_URI} ^/baragain/([^/]+?)$ +RewriteCond %{REQUEST_URI} ^/baragain/([^/]+)$ RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC] RewriteRule .* - [S=1,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_POST:1,E=_ROUTING__allow_HEAD:1] -RewriteCond %{REQUEST_URI} ^/baragain/([^/]+?)$ +RewriteCond %{REQUEST_URI} ^/baragain/([^/]+)$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baragain,E=_ROUTING_foo:%1] # baz @@ -35,25 +35,25 @@ RewriteCond %{REQUEST_URI} ^/test/baz3/$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz3] # baz4 -RewriteCond %{REQUEST_URI} ^/test/([^/]+?)$ +RewriteCond %{REQUEST_URI} ^/test/([^/]+)$ RewriteRule .* $0/ [QSA,L,R=301] -RewriteCond %{REQUEST_URI} ^/test/([^/]+?)/$ +RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz4,E=_ROUTING_foo:%1] # baz5 -RewriteCond %{REQUEST_URI} ^/test/([^/]+?)/$ +RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$ RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] RewriteRule .* - [S=2,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_HEAD:1] -RewriteCond %{REQUEST_URI} ^/test/([^/]+?)$ +RewriteCond %{REQUEST_URI} ^/test/([^/]+)$ RewriteRule .* $0/ [QSA,L,R=301] -RewriteCond %{REQUEST_URI} ^/test/([^/]+?)/$ +RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5,E=_ROUTING_foo:%1] # baz5unsafe -RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+?)/$ +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+)/$ RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC] RewriteRule .* - [S=1,E=_ROUTING__allow_POST:1] -RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+?)/$ +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+)/$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5unsafe,E=_ROUTING_foo:%1] # baz6 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 87ac0d10a0..53e86fa364 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -31,7 +31,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // bar - if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?[^/]+)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_bar; @@ -44,7 +44,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher not_bar: // barhead - if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?[^/]+)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_barhead; @@ -72,14 +72,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // baz4 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+?)/$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+)/$#s', $pathinfo, $matches)) { $matches['_route'] = 'baz4'; return $matches; } // baz5 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+?)/$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+)/$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'POST') { $allow[] = 'POST'; goto not_baz5; @@ -92,7 +92,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher not_baz5: // baz.baz6 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+?)/$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+)/$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'PUT') { $allow[] = 'PUT'; goto not_bazbaz6; @@ -124,14 +124,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (0 === strpos($pathinfo, '/a')) { if (0 === strpos($pathinfo, '/a/b\'b')) { // foo1 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo1'; return $matches; } // bar1 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar1'; return $matches; @@ -148,14 +148,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (0 === strpos($pathinfo, '/a/b\'b')) { // foo2 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo2'; return $matches; } // bar2 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar2'; return $matches; @@ -167,7 +167,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (0 === strpos($pathinfo, '/multi')) { // helloWorld - if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?[^/]+?))?$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?[^/]+))?$#s', $pathinfo, $matches)) { return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld')); } @@ -184,14 +184,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // foo3 - if (preg_match('#^/(?<_locale>[^/]+?)/b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?<_locale>[^/]+)/b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo3'; return $matches; } // bar3 - if (preg_match('#^/(?<_locale>[^/]+?)/b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?<_locale>[^/]+)/b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar3'; return $matches; @@ -203,7 +203,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // foo4 - if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo4'; return $matches; @@ -217,14 +217,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (0 === strpos($pathinfo, '/a/b')) { // b - if (preg_match('#^/a/b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'b'; return $matches; } // c - if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'c'; return $matches; 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 4e8af42f5b..a3c105cc8b 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -31,7 +31,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec } // bar - if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?[^/]+)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_bar; @@ -44,7 +44,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec not_bar: // barhead - if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?[^/]+)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_barhead; @@ -76,7 +76,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec } // baz4 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+?)/?$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+)/?$#s', $pathinfo, $matches)) { if (substr($pathinfo, -1) !== '/') { return $this->redirect($pathinfo.'/', 'baz4'); } @@ -87,7 +87,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec } // baz5 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+?)/$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+)/$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'POST') { $allow[] = 'POST'; goto not_baz5; @@ -100,7 +100,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec not_baz5: // baz.baz6 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+?)/$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?[^/]+)/$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'PUT') { $allow[] = 'PUT'; goto not_bazbaz6; @@ -132,14 +132,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (0 === strpos($pathinfo, '/a')) { if (0 === strpos($pathinfo, '/a/b\'b')) { // foo1 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo1'; return $matches; } // bar1 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar1'; return $matches; @@ -156,14 +156,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (0 === strpos($pathinfo, '/a/b\'b')) { // foo2 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo2'; return $matches; } // bar2 - if (preg_match('#^/a/b\'b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar2'; return $matches; @@ -175,7 +175,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (0 === strpos($pathinfo, '/multi')) { // helloWorld - if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?[^/]+?))?$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?[^/]+))?$#s', $pathinfo, $matches)) { return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld')); } @@ -196,14 +196,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec } // foo3 - if (preg_match('#^/(?<_locale>[^/]+?)/b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?<_locale>[^/]+)/b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo3'; return $matches; } // bar3 - if (preg_match('#^/(?<_locale>[^/]+?)/b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?<_locale>[^/]+)/b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar3'; return $matches; @@ -215,7 +215,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec } // foo4 - if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo4'; return $matches; @@ -229,14 +229,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (0 === strpos($pathinfo, '/a/b')) { // b - if (preg_match('#^/a/b/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'b'; return $matches; } // c - if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'c'; return $matches; 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 ae59f11941..13ffeb95e4 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php @@ -32,7 +32,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // dynamic - if (preg_match('#^/rootprefix/(?[^/]+?)$#s', $pathinfo, $matches)) { + if (preg_match('#^/rootprefix/(?[^/]+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'dynamic'; return $matches; diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index 7245e7909c..c2299975d2 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -43,51 +43,51 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Route with a variable', array('/foo/{bar}'), - '/foo', '#^/foo/(?[^/]+?)$#s', array('bar'), array( - array('variable', '/', '[^/]+?', 'bar'), + '/foo', '#^/foo/(?[^/]+)$#s', array('bar'), array( + array('variable', '/', '[^/]+', 'bar'), array('text', '/foo'), )), array( 'Route with a variable that has a default value', array('/foo/{bar}', array('bar' => 'bar')), - '/foo', '#^/foo(?:/(?[^/]+?))?$#s', array('bar'), array( - array('variable', '/', '[^/]+?', 'bar'), + '/foo', '#^/foo(?:/(?[^/]+))?$#s', array('bar'), array( + array('variable', '/', '[^/]+', 'bar'), array('text', '/foo'), )), array( 'Route with several variables', array('/foo/{bar}/{foobar}'), - '/foo', '#^/foo/(?[^/]+?)/(?[^/]+?)$#s', array('bar', 'foobar'), array( - array('variable', '/', '[^/]+?', 'foobar'), - array('variable', '/', '[^/]+?', 'bar'), + '/foo', '#^/foo/(?[^/]+)/(?[^/]+)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]+', 'foobar'), + array('variable', '/', '[^/]+', 'bar'), array('text', '/foo'), )), array( 'Route with several variables that have default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), - '/foo', '#^/foo(?:/(?[^/]+?)(?:/(?[^/]+?))?)?$#s', array('bar', 'foobar'), array( - array('variable', '/', '[^/]+?', 'foobar'), - array('variable', '/', '[^/]+?', 'bar'), + '/foo', '#^/foo(?:/(?[^/]+)(?:/(?[^/]+))?)?$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]+', 'foobar'), + array('variable', '/', '[^/]+', 'bar'), array('text', '/foo'), )), array( 'Route with several variables but some of them have no default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar')), - '/foo', '#^/foo/(?[^/]+?)/(?[^/]+?)$#s', array('bar', 'foobar'), array( - array('variable', '/', '[^/]+?', 'foobar'), - array('variable', '/', '[^/]+?', 'bar'), + '/foo', '#^/foo/(?[^/]+)/(?[^/]+)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]+', 'foobar'), + array('variable', '/', '[^/]+', 'bar'), array('text', '/foo'), )), array( 'Route with an optional variable as the first segment', array('/{bar}', array('bar' => 'bar')), - '', '#^/(?[^/]+?)?$#s', array('bar'), array( - array('variable', '/', '[^/]+?', 'bar'), + '', '#^/(?[^/]+)?$#s', array('bar'), array( + array('variable', '/', '[^/]+', 'bar'), )), array( @@ -96,6 +96,15 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase '', '#^/(?(foo|bar))?$#s', array('bar'), array( array('variable', '/', '(foo|bar)', 'bar'), )), + + array( + 'Route with a variable in last position', + array('/foo-{bar}'), + '/foo', '#^/foo\-(?[^\-]+)$#s', array('bar'), array( + array('variable', '-', '[^\-]+', 'bar'), + array('text', '/foo'), + )), + ); }