[Routing] added hostname matching support to RouteCompiler
This commit is contained in:
parent
add3658001
commit
402359ba9d
@ -37,11 +37,58 @@ class RouteCompiler implements RouteCompilerInterface
|
||||
*/
|
||||
public function compile(Route $route)
|
||||
{
|
||||
$staticPrefix = null;
|
||||
$hostnameVariables = array();
|
||||
$pathVariables = array();
|
||||
$variables = array();
|
||||
$tokens = array();
|
||||
$regex = null;
|
||||
$hostnameRegex = null;
|
||||
$hostnameTokens = array();
|
||||
|
||||
if (null !== $hostnamePattern = $route->getHostnamePattern()) {
|
||||
|
||||
$result = $this->compilePattern($route, $hostnamePattern, true);
|
||||
|
||||
$hostnameVariables = $result['variables'];
|
||||
$variables = array_merge($variables, $hostnameVariables);
|
||||
|
||||
$hostnameTokens = $result['tokens'];
|
||||
$hostnameRegex = $result['regex'];
|
||||
}
|
||||
|
||||
$pattern = $route->getPattern();
|
||||
|
||||
$result = $this->compilePattern($route, $pattern, false);
|
||||
|
||||
$staticPrefix = $result['staticPrefix'];
|
||||
|
||||
$pathVariables = $result['variables'];
|
||||
$variables = array_merge($variables, $pathVariables);
|
||||
|
||||
$tokens = $result['tokens'];
|
||||
$regex = $result['regex'];
|
||||
|
||||
return new CompiledRoute(
|
||||
$staticPrefix,
|
||||
$regex,
|
||||
$tokens,
|
||||
array_unique($variables),
|
||||
$pathVariables,
|
||||
$hostnameVariables,
|
||||
$hostnameRegex,
|
||||
$hostnameTokens
|
||||
);
|
||||
}
|
||||
|
||||
private function compilePattern(Route $route, $pattern, $isHostname)
|
||||
{
|
||||
$len = strlen($pattern);
|
||||
$tokens = array();
|
||||
$variables = array();
|
||||
$matches = array();
|
||||
$pos = 0;
|
||||
$defaultSeparator = $isHostname ? '.' : '/';
|
||||
|
||||
// Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
|
||||
// in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
|
||||
@ -78,7 +125,11 @@ class RouteCompiler implements RouteCompilerInterface
|
||||
// Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally
|
||||
// part of {_format} when generating the URL, e.g. _format = 'mobile.html'.
|
||||
$nextSeparator = $this->findNextSeparator($followingPattern);
|
||||
$regexp = sprintf('[^/%s]+', '/' !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '');
|
||||
$regexp = sprintf(
|
||||
'[^%s%s]+',
|
||||
preg_quote($defaultSeparator, self::REGEX_DELIMITER),
|
||||
$defaultSeparator !== $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.
|
||||
@ -99,12 +150,14 @@ class RouteCompiler implements RouteCompilerInterface
|
||||
|
||||
// find the first optional token
|
||||
$firstOptional = INF;
|
||||
for ($i = count($tokens) - 1; $i >= 0; $i--) {
|
||||
$token = $tokens[$i];
|
||||
if ('variable' === $token[0] && $route->hasDefault($token[3])) {
|
||||
$firstOptional = $i;
|
||||
} else {
|
||||
break;
|
||||
if (!$isHostname) {
|
||||
for ($i = count($tokens) - 1; $i >= 0; $i--) {
|
||||
$token = $tokens[$i];
|
||||
if ('variable' === $token[0] && $route->hasDefault($token[3])) {
|
||||
$firstOptional = $i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,11 +167,11 @@ class RouteCompiler implements RouteCompilerInterface
|
||||
$regexp .= $this->computeRegexp($tokens, $i, $firstOptional);
|
||||
}
|
||||
|
||||
return new CompiledRoute(
|
||||
'text' === $tokens[0][0] ? $tokens[0][1] : '',
|
||||
self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s',
|
||||
array_reverse($tokens),
|
||||
$variables
|
||||
return array(
|
||||
'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '',
|
||||
'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s',
|
||||
'tokens' => array_reverse($tokens),
|
||||
'variables' => $variables,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -179,4 +179,78 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||
array('1e2')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideCompileWithHostnameData
|
||||
*/
|
||||
public function testCompileWithHostname($name, $arguments, $prefix, $regex, $variables, $pathVariables, $tokens, $hostnameRegex, $hostnameVariables, $hostnameTokens)
|
||||
{
|
||||
$r = new \ReflectionClass('Symfony\\Component\\Routing\\Route');
|
||||
$route = $r->newInstanceArgs($arguments);
|
||||
|
||||
$compiled = $route->compile();
|
||||
$this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)');
|
||||
$this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)');
|
||||
$this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)');
|
||||
$this->assertEquals($pathVariables, $compiled->getPathVariables(), $name.' (path variables)');
|
||||
$this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)');
|
||||
|
||||
|
||||
$this->assertEquals($hostnameRegex, str_replace(array("\n", ' '), '', $compiled->getHostnameRegex()), $name.' (hostname regex)');
|
||||
$this->assertEquals($hostnameVariables, $compiled->getHostnameVariables(), $name.' (hostname variables)');
|
||||
$this->assertEquals($hostnameTokens, $compiled->getHostnameTokens(), $name.' (hostname tokens)');
|
||||
}
|
||||
|
||||
public function provideCompileWithHostnameData()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'Route with hostname pattern',
|
||||
array('/hello', array(), array(), array(), 'www.example.com'),
|
||||
'/hello', '#^/hello$#s', array(), array(), array(
|
||||
array('text', '/hello'),
|
||||
),
|
||||
'#^www\.example\.com$#s', array(), array(
|
||||
array('text', 'www.example.com'),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'Route with hostname pattern and some variables',
|
||||
array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'),
|
||||
'/hello', '#^/hello/(?<name>[^/]++)$#s', array('tld', 'name'), array('name'), array(
|
||||
array('variable', '/', '[^/]++', 'name'),
|
||||
array('text', '/hello'),
|
||||
),
|
||||
'#^www\.example\.(?<tld>[^\.]++)$#s', array('tld'), array(
|
||||
array('variable', '.', '[^\.]++', 'tld'),
|
||||
array('text', 'www.example'),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'Route with variable at begining of hostname',
|
||||
array('/hello', array(), array(), array(), '{locale}.example.{tld}'),
|
||||
'/hello', '#^/hello$#s', array('locale', 'tld'), array(), array(
|
||||
array('text', '/hello'),
|
||||
),
|
||||
'#^(?<locale>[^\.]++)\.example\.(?<tld>[^\.]++)$#s', array('locale', 'tld'), array(
|
||||
array('variable', '.', '[^\.]++', 'tld'),
|
||||
array('text', '.example'),
|
||||
array('variable', '', '[^\.]++', 'locale'),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'Route with hostname variables that has a default value',
|
||||
array('/hello', array('locale' => 'a', 'tld' => 'b'), array(), array(), '{locale}.example.{tld}'),
|
||||
'/hello', '#^/hello$#s', array('locale', 'tld'), array(), array(
|
||||
array('text', '/hello'),
|
||||
),
|
||||
'#^(?<locale>[^\.]++)\.example\.(?<tld>[^\.]++)$#s', array('locale', 'tld'), array(
|
||||
array('variable', '.', '[^\.]++', 'tld'),
|
||||
array('text', '.example'),
|
||||
array('variable', '', '[^\.]++', 'locale'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user