[Routing] improve matching performance by using possesive quantifiers when possible (closes #5471)

My benchmarks showed a performance improvement of 20% when matching routes that make use of possesive quantifiers because it prevents backtracking when it's not needed
This commit is contained in:
Tobias Schultze 2012-09-08 13:51:18 +02:00 committed by Fabien Potencier
parent a3147e9f13
commit 4eee88f22b
6 changed files with 77 additions and 69 deletions

View File

@ -79,6 +79,14 @@ class RouteCompiler implements RouteCompilerInterface
// part of {_format} when generating the URL, e.g. _format = 'mobile.html'. // part of {_format} when generating the URL, e.g. _format = 'mobile.html'.
$nextSeparator = $this->findNextSeparator($followingPattern); $nextSeparator = $this->findNextSeparator($followingPattern);
$regexp = sprintf('[^/%s]+', '/' !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : ''); $regexp = sprintf('[^/%s]+', '/' !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '');
if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
// When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
// quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
// Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
// after it. This optimization cannot be applied when the next char is no real separator or when the next variable is
// directly adjacent, e.g. '/{x}{y}'.
$regexp .= '+';
}
} }
$tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName);

View File

@ -7,21 +7,21 @@ RewriteCond %{REQUEST_URI} ^/foo/(baz|symfony)$
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foo,E=_ROUTING_bar:%1,E=_ROUTING_DEFAULTS_def:test] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foo,E=_ROUTING_bar:%1,E=_ROUTING_DEFAULTS_def:test]
# foobar # foobar
RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]+))?$ RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]++))?$
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foobar,E=_ROUTING_bar:%1,E=_ROUTING_DEFAULTS_bar:toto] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foobar,E=_ROUTING_bar:%1,E=_ROUTING_DEFAULTS_bar:toto]
# bar # bar
RewriteCond %{REQUEST_URI} ^/bar/([^/]+)$ RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$
RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC]
RewriteRule .* - [S=1,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_HEAD:1] 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] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:bar,E=_ROUTING_foo:%1]
# baragain # baragain
RewriteCond %{REQUEST_URI} ^/baragain/([^/]+)$ RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$
RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC] 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] 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] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baragain,E=_ROUTING_foo:%1]
# baz # baz
@ -39,25 +39,25 @@ RewriteCond %{REQUEST_URI} ^/test/baz3/$
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz3] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz3]
# baz4 # baz4
RewriteCond %{REQUEST_URI} ^/test/([^/]+)$ RewriteCond %{REQUEST_URI} ^/test/([^/]++)$
RewriteRule .* $0/ [QSA,L,R=301] 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] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz4,E=_ROUTING_foo:%1]
# baz5 # baz5
RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$ RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$
RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC]
RewriteRule .* - [S=2,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_HEAD:1] 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] 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] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5,E=_ROUTING_foo:%1]
# baz5unsafe # baz5unsafe
RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+)/$ RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$
RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC] RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC]
RewriteRule .* - [S=1,E=_ROUTING__allow_POST:1] 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] RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5unsafe,E=_ROUTING_foo:%1]
# baz6 # baz6

View File

@ -31,7 +31,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
} }
// bar // bar
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD')); $allow = array_merge($allow, array('GET', 'HEAD'));
goto not_bar; goto not_bar;
@ -44,7 +44,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
not_bar: not_bar:
// barhead // barhead
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD')); $allow = array_merge($allow, array('GET', 'HEAD'));
goto not_barhead; goto not_barhead;
@ -72,14 +72,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
} }
// baz4 // baz4
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
$matches['_route'] = 'baz4'; $matches['_route'] = 'baz4';
return $matches; return $matches;
} }
// baz5 // baz5
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'POST') { if ($this->context->getMethod() != 'POST') {
$allow[] = 'POST'; $allow[] = 'POST';
goto not_baz5; goto not_baz5;
@ -92,7 +92,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
not_baz5: not_baz5:
// baz.baz6 // baz.baz6
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'PUT') { if ($this->context->getMethod() != 'PUT') {
$allow[] = 'PUT'; $allow[] = 'PUT';
goto not_bazbaz6; 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')) {
if (0 === strpos($pathinfo, '/a/b\'b')) { if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo1 // foo1
if (preg_match('#^/a/b\'b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo1'; $matches['_route'] = 'foo1';
return $matches; return $matches;
} }
// bar1 // bar1
if (preg_match('#^/a/b\'b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'bar1'; $matches['_route'] = 'bar1';
return $matches; return $matches;
@ -148,14 +148,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
if (0 === strpos($pathinfo, '/a/b\'b')) { if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo2 // foo2
if (preg_match('#^/a/b\'b/(?<foo1>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<foo1>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo2'; $matches['_route'] = 'foo2';
return $matches; return $matches;
} }
// bar2 // bar2
if (preg_match('#^/a/b\'b/(?<bar1>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<bar1>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'bar2'; $matches['_route'] = 'bar2';
return $matches; return $matches;
@ -167,7 +167,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
if (0 === strpos($pathinfo, '/multi')) { if (0 === strpos($pathinfo, '/multi')) {
// helloWorld // helloWorld
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]+))?$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]++))?$#s', $pathinfo, $matches)) {
return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld')); 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 // foo3
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/(?<_locale>[^/]++)/b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo3'; $matches['_route'] = 'foo3';
return $matches; return $matches;
} }
// bar3 // bar3
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/(?<_locale>[^/]++)/b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'bar3'; $matches['_route'] = 'bar3';
return $matches; return $matches;
@ -203,7 +203,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
} }
// foo4 // foo4
if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo4'; $matches['_route'] = 'foo4';
return $matches; return $matches;
@ -217,14 +217,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
if (0 === strpos($pathinfo, '/a/b')) { if (0 === strpos($pathinfo, '/a/b')) {
// b // b
if (preg_match('#^/a/b/(?<var>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'b'; $matches['_route'] = 'b';
return $matches; return $matches;
} }
// c // c
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'c'; $matches['_route'] = 'c';
return $matches; return $matches;

View File

@ -31,7 +31,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
} }
// bar // bar
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD')); $allow = array_merge($allow, array('GET', 'HEAD'));
goto not_bar; goto not_bar;
@ -44,7 +44,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
not_bar: not_bar:
// barhead // barhead
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD')); $allow = array_merge($allow, array('GET', 'HEAD'));
goto not_barhead; goto not_barhead;
@ -76,7 +76,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
} }
// baz4 // baz4
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/?$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/?$#s', $pathinfo, $matches)) {
if (substr($pathinfo, -1) !== '/') { if (substr($pathinfo, -1) !== '/') {
return $this->redirect($pathinfo.'/', 'baz4'); return $this->redirect($pathinfo.'/', 'baz4');
} }
@ -87,7 +87,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
} }
// baz5 // baz5
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'POST') { if ($this->context->getMethod() != 'POST') {
$allow[] = 'POST'; $allow[] = 'POST';
goto not_baz5; goto not_baz5;
@ -100,7 +100,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
not_baz5: not_baz5:
// baz.baz6 // baz.baz6
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'PUT') { if ($this->context->getMethod() != 'PUT') {
$allow[] = 'PUT'; $allow[] = 'PUT';
goto not_bazbaz6; 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')) {
if (0 === strpos($pathinfo, '/a/b\'b')) { if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo1 // foo1
if (preg_match('#^/a/b\'b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo1'; $matches['_route'] = 'foo1';
return $matches; return $matches;
} }
// bar1 // bar1
if (preg_match('#^/a/b\'b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'bar1'; $matches['_route'] = 'bar1';
return $matches; return $matches;
@ -156,14 +156,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if (0 === strpos($pathinfo, '/a/b\'b')) { if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo2 // foo2
if (preg_match('#^/a/b\'b/(?<foo1>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<foo1>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo2'; $matches['_route'] = 'foo2';
return $matches; return $matches;
} }
// bar2 // bar2
if (preg_match('#^/a/b\'b/(?<bar1>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b\'b/(?<bar1>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'bar2'; $matches['_route'] = 'bar2';
return $matches; return $matches;
@ -175,7 +175,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if (0 === strpos($pathinfo, '/multi')) { if (0 === strpos($pathinfo, '/multi')) {
// helloWorld // helloWorld
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]+))?$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]++))?$#s', $pathinfo, $matches)) {
return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld')); 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 // foo3
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/(?<_locale>[^/]++)/b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo3'; $matches['_route'] = 'foo3';
return $matches; return $matches;
} }
// bar3 // bar3
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/(?<_locale>[^/]++)/b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'bar3'; $matches['_route'] = 'bar3';
return $matches; return $matches;
@ -215,7 +215,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
} }
// foo4 // foo4
if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'foo4'; $matches['_route'] = 'foo4';
return $matches; return $matches;
@ -229,14 +229,14 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if (0 === strpos($pathinfo, '/a/b')) { if (0 === strpos($pathinfo, '/a/b')) {
// b // b
if (preg_match('#^/a/b/(?<var>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/a/b/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'b'; $matches['_route'] = 'b';
return $matches; return $matches;
} }
// c // c
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]+)$#s', $pathinfo, $matches)) { if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'c'; $matches['_route'] = 'c';
return $matches; return $matches;

View File

@ -32,7 +32,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
} }
// dynamic // dynamic
if (preg_match('#^/rootprefix/(?<var>[^/]+)$#s', $pathinfo, $matches)) { if (preg_match('#^/rootprefix/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
$matches['_route'] = 'dynamic'; $matches['_route'] = 'dynamic';
return $matches; return $matches;

View File

@ -43,51 +43,51 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
array( array(
'Route with a variable', 'Route with a variable',
array('/foo/{bar}'), array('/foo/{bar}'),
'/foo', '#^/foo/(?<bar>[^/]+)$#s', array('bar'), array( '/foo', '#^/foo/(?<bar>[^/]++)$#s', array('bar'), array(
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
array( array(
'Route with a variable that has a default value', 'Route with a variable that has a default value',
array('/foo/{bar}', array('bar' => 'bar')), array('/foo/{bar}', array('bar' => 'bar')),
'/foo', '#^/foo(?:/(?<bar>[^/]+))?$#s', array('bar'), array( '/foo', '#^/foo(?:/(?<bar>[^/]++))?$#s', array('bar'), array(
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
array( array(
'Route with several variables', 'Route with several variables',
array('/foo/{bar}/{foobar}'), array('/foo/{bar}/{foobar}'),
'/foo', '#^/foo/(?<bar>[^/]+)/(?<foobar>[^/]+)$#s', array('bar', 'foobar'), array( '/foo', '#^/foo/(?<bar>[^/]++)/(?<foobar>[^/]++)$#s', array('bar', 'foobar'), array(
array('variable', '/', '[^/]+', 'foobar'), array('variable', '/', '[^/]++', 'foobar'),
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
array( array(
'Route with several variables that have default values', 'Route with several variables that have default values',
array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')),
'/foo', '#^/foo(?:/(?<bar>[^/]+)(?:/(?<foobar>[^/]+))?)?$#s', array('bar', 'foobar'), array( '/foo', '#^/foo(?:/(?<bar>[^/]++)(?:/(?<foobar>[^/]++))?)?$#s', array('bar', 'foobar'), array(
array('variable', '/', '[^/]+', 'foobar'), array('variable', '/', '[^/]++', 'foobar'),
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
array( array(
'Route with several variables but some of them have no default values', 'Route with several variables but some of them have no default values',
array('/foo/{bar}/{foobar}', array('bar' => 'bar')), array('/foo/{bar}/{foobar}', array('bar' => 'bar')),
'/foo', '#^/foo/(?<bar>[^/]+)/(?<foobar>[^/]+)$#s', array('bar', 'foobar'), array( '/foo', '#^/foo/(?<bar>[^/]++)/(?<foobar>[^/]++)$#s', array('bar', 'foobar'), array(
array('variable', '/', '[^/]+', 'foobar'), array('variable', '/', '[^/]++', 'foobar'),
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
array( array(
'Route with an optional variable as the first segment', 'Route with an optional variable as the first segment',
array('/{bar}', array('bar' => 'bar')), array('/{bar}', array('bar' => 'bar')),
'', '#^/(?<bar>[^/]+)?$#s', array('bar'), array( '', '#^/(?<bar>[^/]++)?$#s', array('bar'), array(
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
)), )),
array( array(
@ -107,16 +107,16 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
array( array(
'Route with only optional variables', 'Route with only optional variables',
array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')), array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')),
'', '#^/(?<foo>[^/]+)?(?:/(?<bar>[^/]+))?$#s', array('foo', 'bar'), array( '', '#^/(?<foo>[^/]++)?(?:/(?<bar>[^/]++))?$#s', array('foo', 'bar'), array(
array('variable', '/', '[^/]+', 'bar'), array('variable', '/', '[^/]++', 'bar'),
array('variable', '/', '[^/]+', 'foo'), array('variable', '/', '[^/]++', 'foo'),
)), )),
array( array(
'Route with a variable in last position', 'Route with a variable in last position',
array('/foo-{bar}'), array('/foo-{bar}'),
'/foo', '#^/foo\-(?<bar>[^/]+)$#s', array('bar'), array( '/foo', '#^/foo\-(?<bar>[^/]++)$#s', array('bar'), array(
array('variable', '-', '[^/]+', 'bar'), array('variable', '-', '[^/]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
@ -132,9 +132,9 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
array( array(
'Route without separator between variables', 'Route without separator between variables',
array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')),
'', '#^/(?<w>[^/\.]+)(?<x>[^/\.]+)(?<y>(y|Y))(?:(?<z>[^/\.]+)(?:\.(?<_format>[^/]+))?)?$#s', array('w', 'x', 'y', 'z', '_format'), array( '', '#^/(?<w>[^/\.]+)(?<x>[^/\.]+)(?<y>(y|Y))(?:(?<z>[^/\.]++)(?:\.(?<_format>[^/]++))?)?$#s', array('w', 'x', 'y', 'z', '_format'), array(
array('variable', '.', '[^/]+', '_format'), array('variable', '.', '[^/]++', '_format'),
array('variable', '', '[^/\.]+', 'z'), array('variable', '', '[^/\.]++', 'z'),
array('variable', '', '(y|Y)', 'y'), array('variable', '', '(y|Y)', 'y'),
array('variable', '', '[^/\.]+', 'x'), array('variable', '', '[^/\.]+', 'x'),
array('variable', '/', '[^/\.]+', 'w'), array('variable', '/', '[^/\.]+', 'w'),
@ -143,9 +143,9 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
array( array(
'Route with a format', 'Route with a format',
array('/foo/{bar}.{_format}'), array('/foo/{bar}.{_format}'),
'/foo', '#^/foo/(?<bar>[^/\.]+)\.(?<_format>[^/]+)$#s', array('bar', '_format'), array( '/foo', '#^/foo/(?<bar>[^/\.]++)\.(?<_format>[^/]++)$#s', array('bar', '_format'), array(
array('variable', '.', '[^/]+', '_format'), array('variable', '.', '[^/]++', '_format'),
array('variable', '/', '[^/\.]+', 'bar'), array('variable', '/', '[^/\.]++', 'bar'),
array('text', '/foo'), array('text', '/foo'),
)), )),
); );