[Routing] simplified route compiler

This commit is contained in:
Fabien Potencier 2011-04-25 12:03:41 +02:00
parent 59c6609aec
commit 7c95bda751
13 changed files with 98 additions and 332 deletions

View File

@ -135,24 +135,6 @@ EOF
$output->writeln(sprintf('<comment>Options</comment> %s', $options));
$output->write('<comment>Regex</comment> ');
$output->writeln(preg_replace('/^ /', '', preg_replace('/^/m', ' ', $route->getRegex())), OutputInterface::OUTPUT_RAW);
$tokens = '';
foreach ($route->getTokens() as $token) {
if (!$tokens) {
$tokens = $this->displayToken($token);
} else {
$tokens .= "\n".str_repeat(' ', 13).$this->displayToken($token);
}
}
$output->writeln(sprintf('<comment>Tokens</comment> %s', $tokens));
}
protected function displayToken($token)
{
$type = array_shift($token);
array_shift($token);
return sprintf('%-10s %s', $type, $this->formatValue($token));
}
protected function formatValue($value)

View File

@ -89,6 +89,8 @@ class UrlGenerator implements UrlGeneratorInterface
*/
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute)
{
$variables = array_flip($variables);
$originParameters = $parameters;
$parameters = array_replace($this->context->getParameters(), $parameters);
$tparams = array_replace($defaults, $parameters);
@ -104,8 +106,8 @@ class UrlGenerator implements UrlGeneratorInterface
if ('variable' === $token[0]) {
if (false === $optional || !isset($defaults[$token[3]]) || (isset($parameters[$token[3]]) && $parameters[$token[3]] != $defaults[$token[3]])) {
// check requirement
if (isset($requirements[$token[3]]) && !preg_match('#^'.$requirements[$token[3]].'$#', $tparams[$token[3]])) {
throw new \InvalidArgumentException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $requirements[$token[3]], $tparams[$token[3]]));
if (!preg_match('#^'.$token[2].'$#', $tparams[$token[3]])) {
throw new \InvalidArgumentException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]]));
}
if ($tparams[$token[3]] || !$optional) {
@ -116,14 +118,8 @@ class UrlGenerator implements UrlGeneratorInterface
$optional = false;
}
} elseif ('text' === $token[0]) {
$url = $token[1].$token[2].$url;
$url = $token[1].$url;
$optional = false;
} else {
// handle custom tokens
if ($segment = call_user_func_array(array($this, 'generateFor'.ucfirst(array_shift($token))), array_merge(array($optional, $tparams), $token))) {
$url = $segment.$url;
$optional = false;
}
}
}

View File

@ -47,13 +47,13 @@ class ApacheMatcherDumper extends MatcherDumper
$compiledRoute = $route->compile();
// prepare the apache regex
$regex = preg_replace('/\?P<.+?>/', '', substr($compiledRoute->getRegex(), 1, -2));
$regex = preg_replace('/\?P<.+?>/', '', substr(str_replace(array("\n", ' '), '', $compiledRoute->getRegex()), 1, -2));
$regex = '^'.preg_quote($options['base_uri']).substr($regex, 1);
$hasTrailingSlash = '/$' == substr($regex, -2) && '^/$' != $regex;
$variables = array('E=_ROUTING__route:'.$name);
foreach (array_keys($compiledRoute->getVariables()) as $i => $variable) {
foreach ($compiledRoute->getVariables() as $i => $variable) {
$variables[] = 'E=_ROUTING_'.$variable.':%'.($i + 1);
}
foreach ($route->getDefaults() as $key => $value) {

View File

@ -60,7 +60,7 @@ class PhpMatcherDumper extends MatcherDumper
$conditions = array();
$hasTrailingSlash = false;
$matches = false;
if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', str_replace(array("\n", ' '), '', $compiledRoute->getRegex()), $m)) {
if ($supportsRedirections && substr($m['url'], -1) === '/') {
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === '%s'", rtrim(str_replace('\\', '', $m['url']), '/'));
$hasTrailingSlash = true;
@ -72,7 +72,7 @@ class PhpMatcherDumper extends MatcherDumper
$conditions[] = sprintf("0 === strpos(\$pathinfo, '%s')", $compiledRoute->getStaticPrefix());
}
$regex = $compiledRoute->getRegex();
$regex = str_replace(array("\n", ' '), '', $compiledRoute->getRegex());
if ($supportsRedirections && $pos = strpos($regex, '/$')) {
$regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
$hasTrailingSlash = true;

View File

@ -31,9 +31,7 @@ class Route
*
* Available options:
*
* * segment_separators: An array of allowed characters for segment separators (/ by default)
* * text_regex: A regex that match a valid text name (.+? by default)
* * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
* * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
*
* @param string $pattern The pattern to match
* @param array $defaults An array of default parameter values
@ -101,8 +99,6 @@ class Route
public function setOptions(array $options)
{
$this->options = array_merge(array(
'segment_separators' => array('/', '.'),
'text_regex' => '.+?',
'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
), $options);

View File

@ -18,15 +18,6 @@ namespace Symfony\Component\Routing;
*/
class RouteCompiler implements RouteCompilerInterface
{
protected $options;
protected $route;
protected $variables;
protected $firstOptional;
protected $segments;
protected $tokens;
protected $staticPrefix;
protected $regex;
/**
* Compiles the current route instance.
*
@ -36,200 +27,67 @@ class RouteCompiler implements RouteCompilerInterface
*/
public function compile(Route $route)
{
$this->route = $route;
$this->firstOptional = 0;
$this->segments = array();
$this->variables = array();
$this->tokens = array();
$this->staticPrefix = '';
$this->regex = '';
$this->options = $this->getOptions();
$this->preCompile();
$this->tokenize();
foreach ($this->tokens as $token) {
call_user_func_array(array($this, 'compileFor'.ucfirst(array_shift($token))), $token);
}
$this->postCompile();
$separator = '';
if (count($this->tokens)) {
$lastToken = $this->tokens[count($this->tokens) - 1];
$separator = 'separator' == $lastToken[0] ? $lastToken[2] : '';
}
$this->regex = "#^".implode("", $this->segments)."".preg_quote($separator, '#')."$#x";
// optimize tokens for generation
$pattern = $route->getPattern();
$len = strlen($pattern);
$tokens = array();
foreach ($this->tokens as $i => $token) {
if ($i + 1 === count($this->tokens) && 'separator' === $token[0]) {
// trailing /
$tokens[] = array('text', $token[2], '', null);
} elseif ('separator' !== $token[0]) {
$tokens[] = $token;
$variables = array();
$pos = 0;
preg_match_all('#.\{([\w\d_]+)\}#', $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);
}
}
$pos = $match[0][1] + strlen($match[0][0]);
$var = $match[1][0];
$tokens = array_reverse($tokens);
return new CompiledRoute($this->route, $this->staticPrefix, $this->regex, $tokens, $this->variables);
}
/**
* Pre-compiles a route.
*/
protected function preCompile()
{
}
/**
* Post-compiles a route.
*/
protected function postCompile()
{
// all segments after the last static segment are optional
// be careful, the n-1 is optional only if n is empty
for ($i = $this->firstOptional, $max = count($this->segments); $i < $max; $i++) {
$this->segments[$i] = (0 == $i ? '/?' : '').str_repeat(' ', $i - $this->firstOptional).'(?:'.$this->segments[$i];
$this->segments[] = str_repeat(' ', $max - $i - 1).')?';
}
$this->staticPrefix = '';
foreach ($this->tokens as $token) {
switch ($token[0]) {
case 'separator':
break;
case 'text':
// text is static
$this->staticPrefix .= $token[1].$token[2];
break;
default:
// everything else indicates variable parts. break switch and for loop
break 2;
}
}
}
/**
* Tokenizes the route.
*
* @throws \InvalidArgumentException When route can't be parsed
*/
private function tokenize()
{
$this->tokens = array();
$buffer = $this->route->getPattern();
$afterASeparator = false;
$currentSeparator = '';
// a route is an array of (separator + variable) or (separator + text) segments
while (strlen($buffer)) {
if (false !== $this->tokenizeBufferBefore($buffer, $tokens, $afterASeparator, $currentSeparator)) {
// a custom token
$this->customToken = true;
} else if ($afterASeparator && preg_match('#^\{([\w\d_]+)\}#', $buffer, $match)) {
// a variable
$this->tokens[] = array('variable', $currentSeparator, $match[0], $match[1]);
$currentSeparator = '';
$buffer = substr($buffer, strlen($match[0]));
$afterASeparator = false;
} else if ($afterASeparator && preg_match('#^('.$this->options['text_regex'].')(?:'.$this->options['segment_separators_regex'].'|$)#', $buffer, $match)) {
// a text
$this->tokens[] = array('text', $currentSeparator, $match[1], null);
$currentSeparator = '';
$buffer = substr($buffer, strlen($match[1]));
$afterASeparator = false;
} else if (!$afterASeparator && preg_match('#^'.$this->options['segment_separators_regex'].'#', $buffer, $match)) {
// a separator
$this->tokens[] = array('separator', $currentSeparator, $match[0], null);
$currentSeparator = $match[0];
$buffer = substr($buffer, strlen($match[0]));
$afterASeparator = true;
} else if (false !== $this->tokenizeBufferAfter($buffer, $tokens, $afterASeparator, $currentSeparator)) {
// a custom token
$this->customToken = true;
if ($req = $route->getRequirement($var)) {
$regexp = $req;
} else {
// parsing problem
throw new \InvalidArgumentException(sprintf('Unable to parse "%s" route near "%s".', $this->route->getPattern(), $buffer));
$regexp = $pos !== $len ? sprintf('[^%s]*?', $pattern[$pos]) : '.*?';
}
$tokens[] = array('variable', $match[0][0][0], $regexp, $var);
$variables[] = $var;
}
if ($pos < $len) {
$tokens[] = array('text', substr($pattern, $pos));
}
// find the first optional token
$firstOptional = INF;
for ($i = count($tokens) - 1; $i >= 0; $i--) {
if ('variable' === $tokens[$i][0] && $route->hasDefault($tokens[$i][3])) {
$firstOptional = $i;
} else {
break;
}
}
}
/**
* Tokenizes the buffer before default logic is applied.
*
* This method must return false if the buffer has not been parsed.
*
* @param string $buffer The current route buffer
* @param array $tokens An array of current tokens
* @param Boolean $afterASeparator Whether the buffer is just after a separator
* @param string $currentSeparator The last matched separator
*
* @return Boolean true if a token has been generated, false otherwise
*/
protected function tokenizeBufferBefore(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
{
return false;
}
/**
* Tokenizes the buffer after default logic is applied.
*
* This method must return false if the buffer has not been parsed.
*
* @param string $buffer The current route buffer
* @param array $tokens An array of current tokens
* @param Boolean $afterASeparator Whether the buffer is just after a separator
* @param string $currentSeparator The last matched separator
*
* @return Boolean true if a token has been generated, false otherwise
*/
protected function tokenizeBufferAfter(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
{
return false;
}
protected function compileForText($separator, $text)
{
$this->firstOptional = count($this->segments) + 1;
$this->segments[] = preg_quote($separator, '#').preg_quote($text, '#');
}
protected function compileForVariable($separator, $name, $variable)
{
if (null === $requirement = $this->route->getRequirement($variable)) {
$requirement = $this->options['variable_content_regex'];
// compute the matching regexp
$regex = '';
$indent = 1;
foreach ($tokens as $i => $token) {
if ('text' === $token[0]) {
$regex .= str_repeat(' ', $indent * 4).preg_quote($token[1], '#')."\n";
} else {
if ($i >= $firstOptional) {
$regex .= str_repeat(' ', $indent * 4)."(?:\n";
++$indent;
}
$regex .= str_repeat(' ', $indent * 4).sprintf("%s(?P<%s>%s)\n", preg_quote($token[1], '#'), $token[3], $token[2]);
}
}
while (--$indent) {
$regex .= str_repeat(' ', $indent * 4).")?\n";
}
$this->segments[] = preg_quote($separator, '#').'(?P<'.$variable.'>'.$requirement.')';
$this->variables[$variable] = $name;
if (!$this->route->hasDefault($variable)) {
$this->firstOptional = count($this->segments);
}
}
protected function compileForSeparator($separator, $regexSeparator)
{
}
private function getOptions()
{
$options = $this->route->getOptions();
// compute some regexes
$quoter = function ($a) { return preg_quote($a, '#'); };
$options['segment_separators_regex'] = '(?:'.implode('|', array_map($quoter, $options['segment_separators'])).')';
$options['variable_content_regex'] = '[^'.implode('', array_map($quoter, $options['segment_separators'])).']+?';
return $options;
return new CompiledRoute(
$route,
'text' === $tokens[0][0] ? $tokens[0][1] : '',
sprintf("#^\n%s$#x", $regex),
array_reverse($tokens),
$variables
);
}
}

View File

@ -37,8 +37,6 @@ class CompiledRouteTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('foo' => 'bar'), $compiled->getDefaults(), '->getDefaults() returns the route defaults');
$this->assertEquals(array('foo' => '\d+'), $compiled->getRequirements(), '->getRequirements() returns the route requirements');
$this->assertEquals(array_merge(array(
'segment_separators' => array('/', '.'),
'text_regex' => '.+?',
'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
), array('foo' => 'bar')), $compiled->getOptions(), '->getOptions() returns the route options');
}

View File

@ -7,10 +7,10 @@ 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]
# baz
@ -28,18 +28,18 @@ 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} !^(post)$ [NC]
RewriteRule .* - [S=2,E=_ROUTING__allow_post: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]
# 405 Method Not Allowed

View File

@ -30,7 +30,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
// bar
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P<foo>[^/\.]+?)$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P<foo>.*?)$#x', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('get', 'head'))) {
$allow = array_merge($allow, array('get', 'head'));
goto not_bar;
@ -56,13 +56,13 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
// baz4
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/\.]+?)/$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/]*?)/$#x', $pathinfo, $matches)) {
$matches['_route'] = 'baz4';
return $matches;
}
// baz5
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/\.]+?)/$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/]*?)/$#x', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'post') {
$allow[] = 'post';
goto not_baz5;
@ -73,7 +73,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
not_baz5:
// baz.baz6
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/\.]+?)/$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/]*?)/$#x', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'put') {
$allow[] = 'put';
goto not_bazbaz6;

View File

@ -30,7 +30,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec
}
// bar
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P<foo>[^/\.]+?)$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P<foo>.*?)$#x', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('get', 'head'))) {
$allow = array_merge($allow, array('get', 'head'));
goto not_bar;
@ -59,7 +59,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec
}
// baz4
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/\.]+?)/?$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/]*?)/?$#x', $pathinfo, $matches)) {
if (substr($pathinfo, -1) !== '/') {
return $this->redirect($pathinfo.'/', 'baz4');
}
@ -68,7 +68,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec
}
// baz5
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/\.]+?)/?$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/]*?)/?$#x', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'post') {
$allow[] = 'post';
goto not_baz5;
@ -82,7 +82,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec
not_baz5:
// baz.baz6
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/\.]+?)/?$#x', $pathinfo, $matches)) {
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P<foo>[^/]*?)/?$#x', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'put') {
$allow[] = 'put';
goto not_bazbaz6;

View File

@ -1,53 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\Routing;
use Symfony\Component\Routing\RouteCompiler as BaseRouteCompiler;
use Symfony\Component\Routing\Route;
class RouteCompiler extends BaseRouteCompiler
{
protected function tokenizeBufferBefore(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
{
if ($afterASeparator && preg_match('#^=([\w\d_]+)#', $buffer, $match)) {
// a labelled variable
$this->tokens[] = array('label', $currentSeparator, $match[0], $match[1]);
$currentSeparator = '';
$buffer = substr($buffer, strlen($match[0]));
$afterASeparator = false;
} else {
return false;
}
}
protected function compileForLabel($separator, $name, $variable)
{
if (null === $requirement = $this->route->getRequirement($variable)) {
$requirement = $this->options['variable_content_regex'];
}
$this->segments[] = preg_quote($separator, '#').$variable.$separator.'(?P<'.$variable.'>'.$requirement.')';
$this->variables[$variable] = $name;
if (!$this->route->getDefault($variable)) {
$this->firstOptional = count($this->segments);
}
}
protected function generateForLabel($optional, $tparams, $separator, $name, $variable)
{
if (!empty($tparams[$variable]) && (!$optional || !isset($this->defaults[$variable]) || $tparams[$variable] != $this->defaults[$variable])) {
return $variable.'/'.urlencode($tparams[$variable]);
}
}
}

View File

@ -13,8 +13,6 @@ namespace Symfony\Tests\Component\Routing;
use Symfony\Component\Routing\Route;
require __DIR__.'/RouteCompiler.php';
class RouteCompilerTest extends \PHPUnit_Framework_TestCase
{
/**
@ -27,7 +25,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
$compiled = $route->compile();
$this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)');
$this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)');
$this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)');
$this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)');
$this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)');
}
@ -39,57 +37,50 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
'Static route',
array('/foo'),
'/foo', '#^/foo$#x', array(), array(
array('text', '/', 'foo', null),
array('text', '/foo'),
)),
array(
'Route with a variable',
array('/foo/{bar}'),
'/foo', '#^/foo/(?P<bar>[^/\.]+?)$#x', array('bar' => '{bar}'), array(
array('variable', '/', '{bar}', 'bar'),
array('text', '/', 'foo', null),
'/foo', '#^/foo/(?P<bar>.*?)$#x', 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(?:/(?P<bar>[^/\.]+?))?$#x', array('bar' => '{bar}'), array(
array('variable', '/', '{bar}', 'bar'),
array('text', '/', 'foo', null),
'/foo', '#^/foo(?:/(?P<bar>.*?))?$#x', array('bar'), array(
array('variable', '/', '.*?', 'bar'),
array('text', '/foo'),
)),
array(
'Route with several variables',
array('/foo/{bar}/{foobar}'),
'/foo', '#^/foo/(?P<bar>[^/\.]+?)/(?P<foobar>[^/\.]+?)$#x', array('bar' => '{bar}', 'foobar' => '{foobar}'), array(
array('variable', '/', '{foobar}', 'foobar'),
array('variable', '/', '{bar}', 'bar'),
array('text', '/', 'foo', null),
'/foo', '#^/foo/(?P<bar>[^/]*?)/(?P<foobar>.*?)$#x', 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(?:/(?P<bar>[^/\.]+?) (?:/(?P<foobar>[^/\.]+?) )?)?$#x', array('bar' => '{bar}', 'foobar' => '{foobar}'), array(
array('variable', '/', '{foobar}', 'foobar'),
array('variable', '/', '{bar}', 'bar'),
array('text', '/', 'foo', null),
'/foo', '#^/foo(?:/(?P<bar>[^/]*?)(?:/(?P<foobar>.*?))?)?$#x', 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/(?P<bar>[^/\.]+?)/(?P<foobar>[^/\.]+?)$#x', array('bar' => '{bar}', 'foobar' => '{foobar}'), array(
array('variable', '/', '{foobar}', 'foobar'),
array('variable', '/', '{bar}', 'bar'),
array('text', '/', 'foo', null),
)),
array(
'Route with a custom token',
array('/=foo', array(), array(), array('compiler_class' => 'Symfony\\Tests\\Component\\Routing\\RouteCompiler')),
'', '#^/foo/(?P<foo>[^/\.]+?)$#x', array('foo' => '=foo'), array(
array('label', '/', '=foo', 'foo'),
'/foo', '#^/foo/(?P<bar>[^/]*?)/(?P<foobar>.*?)$#x', array('bar', 'foobar'), array(
array('variable', '/', '.*?', 'foobar'),
array('variable', '/', '[^/]*?', 'bar'),
array('text', '/foo'),
)),
);
}

View File

@ -41,8 +41,6 @@ class RouteTest extends \PHPUnit_Framework_TestCase
$route = new Route('/{foo}');
$route->setOptions(array('foo' => 'bar'));
$this->assertEquals(array_merge(array(
'segment_separators' => array('/', '.'),
'text_regex' => '.+?',
'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
), array('foo' => 'bar')), $route->getOptions(), '->setOptions() sets the options');
$this->assertEquals($route, $route->setOptions(array()), '->setOptions() implements a fluent interface');