[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)
|
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();
|
$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();
|
$tokens = array();
|
||||||
$variables = array();
|
$variables = array();
|
||||||
$matches = array();
|
$matches = array();
|
||||||
$pos = 0;
|
$pos = 0;
|
||||||
|
$defaultSeparator = $isHostname ? '.' : '/';
|
||||||
|
|
||||||
// Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
|
// 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.
|
// 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
|
// 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'.
|
// 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%s]+',
|
||||||
|
preg_quote($defaultSeparator, self::REGEX_DELIMITER),
|
||||||
|
$defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : ''
|
||||||
|
);
|
||||||
if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
|
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
|
// 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.
|
// 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
|
// find the first optional token
|
||||||
$firstOptional = INF;
|
$firstOptional = INF;
|
||||||
for ($i = count($tokens) - 1; $i >= 0; $i--) {
|
if (!$isHostname) {
|
||||||
$token = $tokens[$i];
|
for ($i = count($tokens) - 1; $i >= 0; $i--) {
|
||||||
if ('variable' === $token[0] && $route->hasDefault($token[3])) {
|
$token = $tokens[$i];
|
||||||
$firstOptional = $i;
|
if ('variable' === $token[0] && $route->hasDefault($token[3])) {
|
||||||
} else {
|
$firstOptional = $i;
|
||||||
break;
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +167,11 @@ class RouteCompiler implements RouteCompilerInterface
|
|||||||
$regexp .= $this->computeRegexp($tokens, $i, $firstOptional);
|
$regexp .= $this->computeRegexp($tokens, $i, $firstOptional);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CompiledRoute(
|
return array(
|
||||||
'text' === $tokens[0][0] ? $tokens[0][1] : '',
|
'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '',
|
||||||
self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s',
|
'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s',
|
||||||
array_reverse($tokens),
|
'tokens' => array_reverse($tokens),
|
||||||
$variables
|
'variables' => $variables,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,4 +179,78 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
|||||||
array('1e2')
|
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