diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php
index 1bc4d0d463..161438674a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php
@@ -15,7 +15,6 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\RouterInterface;
-use Symfony\Component\Console\Output\Output;
/**
* A console command for retrieving information about routes
@@ -82,8 +81,10 @@ EOF
$output->writeln($this->getHelper('formatter')->formatSection('router', 'Current routes'));
- $maxName = 4;
- $maxMethod = 6;
+ $maxName = strlen('name');
+ $maxMethod = strlen('method');
+ $maxHostname = strlen('hostname');
+
foreach ($routes as $name => $route) {
$requirements = $route->getRequirements();
$method = isset($requirements['_method'])
@@ -91,20 +92,18 @@ EOF
? implode(', ', $requirements['_method']) : $requirements['_method']
)
: 'ANY';
+ $hostname = '' !== $route->getHostnamePattern()
+ ? $route->getHostnamePattern() : 'ANY';
- if (strlen($name) > $maxName) {
- $maxName = strlen($name);
- }
-
- if (strlen($method) > $maxMethod) {
- $maxMethod = strlen($method);
- }
+ $maxName = max($maxName, strlen($name));
+ $maxMethod = max($maxMethod, strlen($method));
+ $maxHostname = max($maxHostname, strlen($hostname));
}
- $format = '%-'.$maxName.'s %-'.$maxMethod.'s %s';
+ $format = '%-'.$maxName.'s %-'.$maxMethod.'s %-'.$maxHostname.'s %s';
// displays the generated routes
- $format1 = '%-'.($maxName + 19).'s %-'.($maxMethod + 19).'s %s';
- $output->writeln(sprintf($format1, 'Name', 'Method', 'Pattern'));
+ $format1 = '%-'.($maxName + 19).'s %-'.($maxMethod + 19).'s %-'.($maxHostname + 19).'s %s';
+ $output->writeln(sprintf($format1, 'Name', 'Method', 'Hostname', 'Pattern'));
foreach ($routes as $name => $route) {
$requirements = $route->getRequirements();
$method = isset($requirements['_method'])
@@ -112,7 +111,9 @@ EOF
? implode(', ', $requirements['_method']) : $requirements['_method']
)
: 'ANY';
- $output->writeln(sprintf($format, $name, $method, $route->getPattern()));
+ $hostname = '' !== $route->getHostnamePattern()
+ ? $route->getHostnamePattern() : 'ANY';
+ $output->writeln(sprintf($format, $name, $method, $hostname, $route->getPattern()));
}
}
diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php
index f60af463f2..e593f97ff6 100644
--- a/src/Symfony/Component/Routing/Annotation/Route.php
+++ b/src/Symfony/Component/Routing/Annotation/Route.php
@@ -25,6 +25,7 @@ class Route
private $requirements;
private $options;
private $defaults;
+ private $hostnamePattern;
/**
* Constructor.
@@ -43,7 +44,7 @@ class Route
}
foreach ($data as $key => $value) {
- $method = 'set'.$key;
+ $method = 'set'.str_replace('_', '', $key);
if (!method_exists($this, $method)) {
throw new \BadMethodCallException(sprintf("Unknown property '%s' on annotation '%s'.", $key, get_class($this)));
}
@@ -61,6 +62,16 @@ class Route
return $this->pattern;
}
+ public function setHostnamePattern($pattern)
+ {
+ $this->hostnamePattern = $pattern;
+ }
+
+ public function getHostnamePattern()
+ {
+ return $this->hostnamePattern;
+ }
+
public function setName($name)
{
$this->name = $name;
diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php
index 54d4e2ce55..1ccad3c22a 100644
--- a/src/Symfony/Component/Routing/CompiledRoute.php
+++ b/src/Symfony/Component/Routing/CompiledRoute.php
@@ -22,20 +22,32 @@ class CompiledRoute
private $tokens;
private $staticPrefix;
private $regex;
+ private $pathVariables;
+ private $hostnameVariables;
+ private $hostnameRegex;
+ private $hostnameTokens;
/**
* Constructor.
*
- * @param string $staticPrefix The static prefix of the compiled route
- * @param string $regex The regular expression to use to match this route
- * @param array $tokens An array of tokens to use to generate URL for this route
- * @param array $variables An array of variables
+ * @param string $staticPrefix The static prefix of the compiled route
+ * @param string $regex The regular expression to use to match this route
+ * @param array $tokens An array of tokens to use to generate URL for this route
+ * @param array $pathVariables An array of path variables
+ * @param string|null $hostnameRegex Hostname regex
+ * @param array $hostnameTokens Hostname tokens
+ * @param array $hostnameVariables An array of hostname variables
+ * @param array $variables An array of variables (variables defined in the path and in the hostname patterns)
*/
- public function __construct($staticPrefix, $regex, array $tokens, array $variables)
+ public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostnameRegex = null, array $hostnameTokens = array(), array $hostnameVariables = array(), array $variables = array())
{
- $this->staticPrefix = $staticPrefix;
+ $this->staticPrefix = (string) $staticPrefix;
$this->regex = $regex;
$this->tokens = $tokens;
+ $this->pathVariables = $pathVariables;
+ $this->hostnameRegex = $hostnameRegex;
+ $this->hostnameTokens = $hostnameTokens;
+ $this->hostnameVariables = $hostnameVariables;
$this->variables = $variables;
}
@@ -59,6 +71,16 @@ class CompiledRoute
return $this->regex;
}
+ /**
+ * Returns the hostname regex
+ *
+ * @return string|null The hostname regex or null
+ */
+ public function getHostnameRegex()
+ {
+ return $this->hostnameRegex;
+ }
+
/**
* Returns the tokens.
*
@@ -69,6 +91,16 @@ class CompiledRoute
return $this->tokens;
}
+ /**
+ * Returns the hostname tokens.
+ *
+ * @return array The tokens
+ */
+ public function getHostnameTokens()
+ {
+ return $this->hostnameTokens;
+ }
+
/**
* Returns the variables.
*
@@ -78,4 +110,25 @@ class CompiledRoute
{
return $this->variables;
}
+
+ /**
+ * Returns the path variables.
+ *
+ * @return array The variables
+ */
+ public function getPathVariables()
+ {
+ return $this->pathVariables;
+ }
+
+ /**
+ * Returns the hostname variables.
+ *
+ * @return array The variables
+ */
+ public function getHostnameVariables()
+ {
+ return $this->hostnameVariables;
+ }
+
}
diff --git a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php
index 49acecad41..c85f0201e4 100644
--- a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php
+++ b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php
@@ -91,6 +91,7 @@ EOF;
$properties[] = $route->getDefaults();
$properties[] = $route->getRequirements();
$properties[] = $compiledRoute->getTokens();
+ $properties[] = $compiledRoute->getHostnameTokens();
$routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true)));
}
@@ -113,9 +114,9 @@ EOF;
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', \$name));
}
- list(\$variables, \$defaults, \$requirements, \$tokens) = self::\$declaredRoutes[\$name];
+ list(\$variables, \$defaults, \$requirements, \$tokens, \$hostnameTokens) = self::\$declaredRoutes[\$name];
- return \$this->doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$absolute);
+ return \$this->doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$absolute, \$hostnameTokens);
}
EOF;
}
diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php
index 1c15fd80db..535e363e88 100644
--- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php
+++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php
@@ -122,14 +122,14 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
// the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile();
- return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $absolute);
+ return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $absolute, $compiledRoute->getHostnameTokens());
}
/**
* @throws MissingMandatoryParametersException When route has some missing mandatory parameters
* @throws InvalidParameterException When a parameter value is not correct
*/
- protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute)
+ protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute, $hostnameTokens)
{
$variables = array_flip($variables);
$mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
@@ -191,13 +191,43 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$url .= '?'.$query;
}
- if ($this->context->getHost()) {
+ if ($host = $this->context->getHost()) {
$scheme = $this->context->getScheme();
if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme != $req) {
$absolute = true;
$scheme = $req;
}
+ if ($hostnameTokens) {
+ $routeHost = '';
+ foreach ($hostnameTokens as $token) {
+ if ('variable' === $token[0]) {
+ if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) {
+ $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $mergedParams[$token[3]]);
+
+ if ($this->strictRequirements) {
+ throw new InvalidParameterException($message);
+ }
+
+ if ($this->logger) {
+ $this->logger->err($message);
+ }
+
+ return null;
+ }
+
+ $routeHost = $token[1].$mergedParams[$token[3]].$routeHost;
+ } elseif ('text' === $token[0]) {
+ $routeHost = $token[1].$routeHost;
+ }
+ }
+
+ if ($routeHost != $host) {
+ $host = $routeHost;
+ $absolute = true;
+ }
+ }
+
if ($absolute) {
$port = '';
if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
@@ -206,7 +236,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$port = ':'.$this->context->getHttpsPort();
}
- $url = $scheme.'://'.$this->context->getHost().$port.$url;
+ $url = $scheme.'://'.$host.$port.$url;
}
}
diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
index 3ef006b057..7fb66a6124 100644
--- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
+++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
@@ -57,7 +57,7 @@ use Symfony\Component\Config\Loader\LoaderResolverInterface;
abstract class AnnotationClassLoader implements LoaderInterface
{
protected $reader;
- protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route';
+ protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route';
protected $defaultRouteIndex;
/**
@@ -97,10 +97,11 @@ abstract class AnnotationClassLoader implements LoaderInterface
}
$globals = array(
- 'pattern' => '',
- 'requirements' => array(),
- 'options' => array(),
- 'defaults' => array(),
+ 'pattern' => '',
+ 'requirements' => array(),
+ 'options' => array(),
+ 'defaults' => array(),
+ 'hostname_pattern' => '',
);
$class = new \ReflectionClass($class);
@@ -124,6 +125,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
if (null !== $annot->getDefaults()) {
$globals['defaults'] = $annot->getDefaults();
}
+
+ if (null !== $annot->getHostnamePattern()) {
+ $globals['hostname_pattern'] = $annot->getHostnamePattern();
+ }
}
$collection = new RouteCollection();
@@ -148,16 +153,21 @@ abstract class AnnotationClassLoader implements LoaderInterface
$name = $this->getDefaultRouteName($class, $method);
}
- $defaults = array_merge($globals['defaults'], $annot->getDefaults());
+ $defaults = array_replace($globals['defaults'], $annot->getDefaults());
foreach ($method->getParameters() as $param) {
if ($param->isOptional()) {
$defaults[$param->getName()] = $param->getDefaultValue();
}
}
- $requirements = array_merge($globals['requirements'], $annot->getRequirements());
- $options = array_merge($globals['options'], $annot->getOptions());
+ $requirements = array_replace($globals['requirements'], $annot->getRequirements());
+ $options = array_replace($globals['options'], $annot->getOptions());
- $route = new Route($globals['pattern'].$annot->getPattern(), $defaults, $requirements, $options);
+ $hostnamePattern = $annot->getHostnamePattern();
+ if (null === $hostnamePattern) {
+ $hostnamePattern = $globals['hostname_pattern'];
+ }
+
+ $route = new Route($globals['pattern'].$annot->getPattern(), $defaults, $requirements, $options, $hostnamePattern);
$this->configureRoute($route, $class, $method, $annot);
diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
index 695494bd6e..9c196a8d07 100644
--- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
@@ -73,9 +73,10 @@ class XmlFileLoader extends FileLoader
$this->parseRoute($collection, $node, $path);
break;
case 'import':
- $resource = (string) $node->getAttribute('resource');
- $type = (string) $node->getAttribute('type');
- $prefix = (string) $node->getAttribute('prefix');
+ $resource = $node->getAttribute('resource');
+ $type = $node->getAttribute('type');
+ $prefix = $node->getAttribute('prefix');
+ $hostnamePattern = $node->getAttribute('hostname-pattern');
$defaults = array();
$requirements = array();
@@ -88,13 +89,13 @@ class XmlFileLoader extends FileLoader
switch ($n->tagName) {
case 'default':
- $defaults[(string) $n->getAttribute('key')] = trim((string) $n->nodeValue);
+ $defaults[$n->getAttribute('key')] = trim($n->nodeValue);
break;
case 'requirement':
- $requirements[(string) $n->getAttribute('key')] = trim((string) $n->nodeValue);
+ $requirements[$n->getAttribute('key')] = trim($n->nodeValue);
break;
case 'option':
- $options[(string) $n->getAttribute('key')] = trim((string) $n->nodeValue);
+ $options[$n->getAttribute('key')] = trim($n->nodeValue);
break;
default:
throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $n->tagName));
@@ -102,7 +103,7 @@ class XmlFileLoader extends FileLoader
}
$this->setCurrentDir(dirname($path));
- $collection->addCollection($this->import($resource, ('' !== $type ? $type : null), false, $file), $prefix, $defaults, $requirements, $options);
+ $collection->addCollection($this->import($resource, ('' !== $type ? $type : null), false, $file), $prefix, $defaults, $requirements, $options, $hostnamePattern);
break;
default:
throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
@@ -141,22 +142,22 @@ class XmlFileLoader extends FileLoader
switch ($node->tagName) {
case 'default':
- $defaults[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
+ $defaults[$node->getAttribute('key')] = trim((string) $node->nodeValue);
break;
case 'option':
- $options[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
+ $options[$node->getAttribute('key')] = trim((string) $node->nodeValue);
break;
case 'requirement':
- $requirements[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
+ $requirements[$node->getAttribute('key')] = trim((string) $node->nodeValue);
break;
default:
throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
}
}
- $route = new Route((string) $definition->getAttribute('pattern'), $defaults, $requirements, $options);
+ $route = new Route($definition->getAttribute('pattern'), $defaults, $requirements, $options, $definition->getAttribute('hostname-pattern'));
- $collection->add((string) $definition->getAttribute('id'), $route);
+ $collection->add($definition->getAttribute('id'), $route);
}
/**
diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
index d6d0539b17..cf981d2b57 100644
--- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
@@ -27,7 +27,7 @@ use Symfony\Component\Config\Loader\FileLoader;
class YamlFileLoader extends FileLoader
{
private static $availableKeys = array(
- 'type', 'resource', 'prefix', 'pattern', 'options', 'defaults', 'requirements'
+ 'type', 'resource', 'prefix', 'pattern', 'options', 'defaults', 'requirements', 'hostname_pattern',
);
/**
@@ -70,9 +70,10 @@ class YamlFileLoader extends FileLoader
$defaults = isset($config['defaults']) ? $config['defaults'] : array();
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
+ $hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : '';
$this->setCurrentDir(dirname($path));
- $collection->addCollection($this->import($config['resource'], $type, false, $file), $prefix, $defaults, $requirements, $options);
+ $collection->addCollection($this->import($config['resource'], $type, false, $file), $prefix, $defaults, $requirements, $options, $hostnamePattern);
} else {
$this->parseRoute($collection, $name, $config, $path);
}
@@ -106,12 +107,13 @@ class YamlFileLoader extends FileLoader
$defaults = isset($config['defaults']) ? $config['defaults'] : array();
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
+ $hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : null;
if (!isset($config['pattern'])) {
throw new \InvalidArgumentException(sprintf('You must define a "pattern" for the "%s" route.', $name));
}
- $route = new Route($config['pattern'], $defaults, $requirements, $options);
+ $route = new Route($config['pattern'], $defaults, $requirements, $options, $hostnamePattern);
$collection->add($name, $route);
}
diff --git a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
index b8571e661d..e1a97654d6 100644
--- a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
+++ b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
@@ -23,6 +23,7 @@
+
@@ -36,6 +37,7 @@
+
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
index e9fc82d458..73ace8afa6 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
@@ -46,17 +46,55 @@ class ApacheMatcherDumper extends MatcherDumper
$rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]");
$methodVars = array();
+ $hostnameRegexUnique = 0;
+ $prevHosnameRegex = '';
foreach ($this->getRoutes()->all() as $name => $route) {
- $rules[] = $this->dumpRoute($name, $route, $options);
- $methodVars = array_merge($methodVars, $this->getRouteMethods($route));
- }
+ $compiledRoute = $route->compile();
+ $hostnameRegex = $compiledRoute->getHostnameRegex();
+
+ if (null !== $hostnameRegex && $prevHosnameRegex !== $hostnameRegex) {
+
+ $prevHosnameRegex = $hostnameRegex;
+ $hostnameRegexUnique++;
+
+ $rule = array();
+
+ $regex = $this->regexToApacheRegex($hostnameRegex);
+ $regex = self::escape($regex, ' ', '\\');
+
+ $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex);
+
+ $variables = array();
+ $variables[] = sprintf('E=__ROUTING_hostname_%s:1', $hostnameRegexUnique);
+
+ foreach ($compiledRoute->getHostnameVariables() as $i => $variable) {
+ $variables[] = sprintf('E=__ROUTING_hostname_%s_%s:%%%d', $hostnameRegexUnique, $variable, $i+1);
+ }
+
+ $variables = implode(',', $variables);
+
+ $rule[] = sprintf('RewriteRule .? - [%s]', $variables);
+
+ $rules[] = implode("\n", $rule);
+ }
+
+ $rules[] = $this->dumpRoute($name, $route, $options, $hostnameRegexUnique);
+
+ if ($req = $route->getRequirement('_method')) {
+ $methods = explode('|', strtoupper($req));
+ $methodVars = array_merge($methodVars, $methods);
+ }
+ }
if (0 < count($methodVars)) {
$rule = array('# 405 Method Not Allowed');
$methodVars = array_values(array_unique($methodVars));
+ if (in_array('GET', $methodVars) && !in_array('HEAD', $methodVars)) {
+ $methodVars[] = 'HEAD';
+ }
foreach ($methodVars as $i => $methodVar) {
- $rule[] = sprintf('RewriteCond %%{_ROUTING_allow_%s} !-z%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : '');
+ $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : '');
}
$rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']);
@@ -66,7 +104,17 @@ class ApacheMatcherDumper extends MatcherDumper
return implode("\n\n", $rules)."\n";
}
- private function dumpRoute($name, $route, array $options)
+ /**
+ * Dumps a single route
+ *
+ * @param string $name Route name
+ * @param Route $route The route
+ * @param array $options Options
+ * @param bool $hostnameRegexUnique Unique identifier for the hostname regex
+ *
+ * @return string The compiled route
+ */
+ private function dumpRoute($name, $route, array $options, $hostnameRegexUnique)
{
$compiledRoute = $route->compile();
@@ -79,7 +127,10 @@ class ApacheMatcherDumper extends MatcherDumper
$hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex;
$variables = array('E=_ROUTING_route:'.$name);
- foreach ($compiledRoute->getVariables() as $i => $variable) {
+ foreach ($compiledRoute->getHostnameVariables() as $variable) {
+ $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_hostname_%s_%s}', $variable, $hostnameRegexUnique, $variable);
+ }
+ foreach ($compiledRoute->getPathVariables() as $i => $variable) {
$variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1);
}
foreach ($route->getDefaults() as $key => $value) {
@@ -98,10 +149,13 @@ class ApacheMatcherDumper extends MatcherDumper
if (0 < count($methods)) {
$allow = array();
foreach ($methods as $method) {
- $methodVars[] = $method;
$allow[] = 'E=_ROUTING_allow_'.$method.':1';
}
+ if ($hostnameRegex = $compiledRoute->getHostnameRegex()) {
+ $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique);
+ }
+
$rule[] = "RewriteCond %{REQUEST_URI} $regex";
$rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]", implode('|', $methods));
$rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow));
@@ -109,11 +163,21 @@ class ApacheMatcherDumper extends MatcherDumper
// redirect with trailing slash appended
if ($hasTrailingSlash) {
+
+ if ($hostnameRegex = $compiledRoute->getHostnameRegex()) {
+ $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique);
+ }
+
$rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$';
$rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]';
}
// the main rule
+
+ if ($hostnameRegex = $compiledRoute->getHostnameRegex()) {
+ $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique);
+ }
+
$rule[] = "RewriteCond %{REQUEST_URI} $regex";
$rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]";
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php
index c75e9a0be8..8b36d0896a 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php
@@ -20,6 +20,7 @@ class DumperCollection implements \IteratorAggregate
{
private $parent;
private $children = array();
+ private $attributes = array();
/**
* Returns the children routes and collections.
@@ -45,7 +46,7 @@ class DumperCollection implements \IteratorAggregate
}
/**
- * Sets children
+ * Sets children.
*
* @param array $children The children
*/
@@ -98,4 +99,50 @@ class DumperCollection implements \IteratorAggregate
{
$this->parent = $parent;
}
+
+ /**
+ * Returns true if the attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return Boolean true if the attribute is defined, false otherwise
+ */
+ public function hasAttribute($name)
+ {
+ return array_key_exists($name, $this->attributes);
+ }
+
+ /**
+ * Returns an attribute by name.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default Default value is the attribute doesn't exist
+ *
+ * @return mixed The attribute value
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return $this->hasAttribute($name) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * Sets an attribute by name.
+ *
+ * @param string $name The attribute name
+ * @param mixed $value The attribute value
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * Sets multiple attributes.
+ *
+ * @param array $attributes The attributes
+ */
+ public function setAttributes($attributes)
+ {
+ $this->attributes = $attributes;
+ }
}
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
index df678bcec8..12aa644694 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
@@ -37,7 +37,7 @@ class PhpMatcherDumper extends MatcherDumper
*/
public function dump(array $options = array())
{
- $options = array_merge(array(
+ $options = array_replace(array(
'class' => 'ProjectUrlMatcher',
'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
), $options);
@@ -109,18 +109,44 @@ EOF;
*/
private function compileRoutes(RouteCollection $routes, $supportsRedirections)
{
- $collection = $this->flattenRouteCollection($routes);
- $tree = $this->buildPrefixTree($collection);
+ $fetchedHostname = false;
- return $this->compilePrefixRoutes($tree, $supportsRedirections);
+ $routes = $this->flattenRouteCollection($routes);
+ $groups = $this->groupRoutesByHostnameRegex($routes);
+ $code = '';
+
+ foreach ($groups as $collection) {
+ if (null !== $regex = $collection->getAttribute('hostname_regex')) {
+ if (!$fetchedHostname) {
+ $code .= " \$hostname = \$this->context->getHost();\n\n";
+ $fetchedHostname = true;
+ }
+
+ $code .= sprintf(" if (preg_match(%s, \$hostname, \$hostnameMatches)) {\n", var_export($regex, true));
+ }
+
+ $tree = $this->buildPrefixTree($collection);
+ $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections);
+
+ if (null !== $regex) {
+ // apply extra indention at each line (except empty ones)
+ $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode);
+ $code .= $groupCode;
+ $code .= " }\n\n";
+ } else {
+ $code .= $groupCode;
+ }
+ }
+
+ return $code;
}
/**
* Generates PHP code recursively to match a tree of routes
*
- * @param DumperPrefixCollection $routes A DumperPrefixCollection instance
+ * @param DumperPrefixCollection $routes A DumperPrefixCollection instance
* @param Boolean $supportsRedirections Whether redirections are supported by the base class
- * @parma string $prefix Prefix of the parent collection
+ * @parma string $prefix Prefix of the parent collection
*
* @return string PHP code
*/
@@ -171,6 +197,7 @@ EOF;
$conditions = array();
$hasTrailingSlash = false;
$matches = false;
+ $hostnameMatches = false;
$methods = array();
if ($req = $route->getRequirement('_method')) {
@@ -183,7 +210,7 @@ EOF;
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
- if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
+ if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
if ($supportsTrailingSlash && substr($m['url'], -1) === '/') {
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
$hasTrailingSlash = true;
@@ -205,6 +232,10 @@ EOF;
$matches = true;
}
+ if ($compiledRoute->getHostnameVariables()) {
+ $hostnameMatches = true;
+ }
+
$conditions = implode(' && ', $conditions);
$code .= <<getDefaults()) {
- $code .= sprintf(" return array_merge(\$this->mergeDefaults(\$matches, %s), array('_route' => '%s'));\n"
- , str_replace("\n", '', var_export($route->getDefaults(), true)), $name);
- } elseif (true === $matches) {
- $code .= sprintf(" \$matches['_route'] = '%s';\n\n", $name);
- $code .= " return \$matches;\n";
+ if ($matches || $hostnameMatches) {
+ $vars = array();
+ if ($hostnameMatches) {
+ $vars[] = '$hostnameMatches';
+ }
+ if ($matches) {
+ $vars[] = '$matches';
+ }
+ $vars[] = "array('_route' => '$name')";
+
+ $code .= sprintf(" return \$this->mergeDefaults(array_replace(%s), %s);\n"
+ , implode(', ', $vars), str_replace("\n", '', var_export($route->getDefaults(), true)));
+
} elseif ($route->getDefaults()) {
- $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_merge($route->getDefaults(), array('_route' => $name)), true)));
+ $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
} else {
$code .= sprintf(" return array('_route' => '%s');\n", $name);
}
@@ -284,10 +322,10 @@ EOF;
}
/**
- * Flattens a tree of routes to a single collection
+ * Flattens a tree of routes to a single collection.
*
- * @param RouteCollection $routes Collection of routes
- * @param DumperCollection $to A DumperCollection to add routes to
+ * @param RouteCollection $routes Collection of routes
+ * @param DumperCollection $to A DumperCollection to add routes to
*
* @return DumperCollection
*/
@@ -308,13 +346,43 @@ EOF;
return $to;
}
+ /**
+ * Groups consecutive routes having the same hostname regex.
+ *
+ * The results is a collection of collections of routes having the same hostname regex.
+ *
+ * @param DumperCollection $routes Flat collection of DumperRoutes
+ *
+ * @return DumperCollection A collection with routes grouped by hostname regex in sub-collections
+ */
+ private function groupRoutesByHostnameRegex(DumperCollection $routes)
+ {
+ $groups = new DumperCollection();
+
+ $currentGroup = new DumperCollection();
+ $currentGroup->setAttribute('hostname_regex', null);
+ $groups->add($currentGroup);
+
+ foreach ($routes as $route) {
+ $hostnameRegex = $route->getRoute()->compile()->getHostnameRegex();
+ if ($currentGroup->getAttribute('hostname_regex') !== $hostnameRegex) {
+ $currentGroup = new DumperCollection();
+ $currentGroup->setAttribute('hostname_regex', $hostnameRegex);
+ $groups->add($currentGroup);
+ }
+ $currentGroup->add($route);
+ }
+
+ return $groups;
+ }
+
/**
* Organizes the routes into a prefix tree.
*
* Routes order is preserved such that traversing the tree will traverse the
* routes in the origin order
*
- * @param DumperCollection $collection A collection of routes
+ * @param DumperCollection $collection A collection of routes
*
* @return DumperPrefixCollection
*/
@@ -331,5 +399,4 @@ EOF;
return $tree;
}
-
}
diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
index 8e757bf3d7..2afe0f738e 100644
--- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
+++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
@@ -118,6 +118,11 @@ class UrlMatcher implements UrlMatcherInterface
continue;
}
+ $hostnameMatches = array();
+ if ($compiledRoute->getHostnameRegex() && !preg_match($compiledRoute->getHostnameRegex(), $this->context->getHost(), $hostnameMatches)) {
+ continue;
+ }
+
// check HTTP method requirement
if ($req = $route->getRequirement('_method')) {
// HEAD and GET are equivalent as per RFC
@@ -142,7 +147,7 @@ class UrlMatcher implements UrlMatcherInterface
continue;
}
- return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name));
+ return $this->mergeDefaults(array_replace($matches, $hostnameMatches, array('_route' => $name)), $route->getDefaults());
}
}
diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php
index 775be741d3..a13c3c77b7 100644
--- a/src/Symfony/Component/Routing/Route.php
+++ b/src/Symfony/Component/Routing/Route.php
@@ -25,6 +25,7 @@ class Route implements \Serializable
private $requirements;
private $options;
private $compiled;
+ private $hostnamePattern;
private static $compilers = array();
@@ -39,15 +40,17 @@ class Route implements \Serializable
* @param array $defaults An array of default parameter values
* @param array $requirements An array of requirements for parameters (regexes)
* @param array $options An array of options
+ * @param string $hostname The hostname pattern to match
*
* @api
*/
- public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array())
+ public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array(), $hostnamePattern = '')
{
$this->setPattern($pattern);
$this->setDefaults($defaults);
$this->setRequirements($requirements);
$this->setOptions($options);
+ $this->setHostnamePattern($hostnamePattern);
}
public function __clone()
@@ -103,6 +106,28 @@ class Route implements \Serializable
return $this;
}
+ /**
+ * Returns the hostname pattern.
+ *
+ * @return string The pattern
+ */
+ public function getHostnamePattern()
+ {
+ return $this->hostnamePattern;
+ }
+
+ /**
+ * Sets the hostname pattern.
+ *
+ * @param string $pattern The pattern
+ */
+ public function setHostnamePattern($pattern)
+ {
+ $this->hostnamePattern = (string) $pattern;
+
+ return $this;
+ }
+
/**
* Returns the options.
*
diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php
index 17be00e51e..7fc0304211 100644
--- a/src/Symfony/Component/Routing/RouteCollection.php
+++ b/src/Symfony/Component/Routing/RouteCollection.php
@@ -30,6 +30,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
private $resources;
private $prefix;
private $parent;
+ private $hostnamePattern;
/**
* Constructor.
@@ -41,6 +42,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
$this->routes = array();
$this->resources = array();
$this->prefix = '';
+ $this->hostnamePattern = '';
}
public function __clone()
@@ -183,17 +185,18 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* Adds a route collection to the current set of routes (at the end of the current set).
*
- * @param RouteCollection $collection A RouteCollection instance
- * @param string $prefix An optional prefix to add before each pattern of the route collection
- * @param array $defaults An array of default values
- * @param array $requirements An array of requirements
- * @param array $options An array of options
+ * @param RouteCollection $collection A RouteCollection instance
+ * @param string $prefix An optional prefix to add before each pattern of the route collection
+ * @param array $defaults An array of default values
+ * @param array $requirements An array of requirements
+ * @param array $options An array of options
+ * @param string $hostnamePattern Hostname pattern
*
* @throws \InvalidArgumentException When the RouteCollection already exists in the tree
*
* @api
*/
- public function addCollection(RouteCollection $collection, $prefix = '', $defaults = array(), $requirements = array(), $options = array())
+ public function addCollection(RouteCollection $collection, $prefix = '', $defaults = array(), $requirements = array(), $options = array(), $hostnamePattern = '')
{
// prevent infinite loops by recursive referencing
$root = $this->getRoot();
@@ -208,6 +211,11 @@ class RouteCollection implements \IteratorAggregate, \Countable
// the sub-collection must have the prefix of the parent (current instance) prepended because it does not
// necessarily already have it applied (depending on the order RouteCollections are added to each other)
$collection->addPrefix($this->getPrefix() . $prefix, $defaults, $requirements, $options);
+
+ if ('' !== $hostnamePattern) {
+ $collection->setHostnamePattern($hostnamePattern);
+ }
+
$this->routes[] = $collection;
}
@@ -259,6 +267,30 @@ class RouteCollection implements \IteratorAggregate, \Countable
return $this->prefix;
}
+ /**
+ * Returns the hostname pattern.
+ *
+ * @return string The pattern
+ */
+ public function getHostnamePattern()
+ {
+ return $this->hostnamePattern;
+ }
+
+ /**
+ * Sets the hostname pattern on this collection and all children.
+ *
+ * @param string $pattern The pattern
+ */
+ public function setHostnamePattern($pattern)
+ {
+ $this->hostnamePattern = (string) $pattern;
+
+ foreach ($this->routes as $name => $route) {
+ $route->setHostnamePattern($pattern);
+ }
+ }
+
/**
* Returns an array of resources loaded to build this collection.
*
diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php
index 03d3e38644..e53e23e78e 100644
--- a/src/Symfony/Component/Routing/RouteCompiler.php
+++ b/src/Symfony/Component/Routing/RouteCompiler.php
@@ -37,11 +37,57 @@ 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 ('' !== $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,
+ $pathVariables,
+ $hostnameRegex,
+ $hostnameTokens,
+ $hostnameVariables,
+ array_unique($variables)
+ );
+ }
+
+ 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 +124,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 +149,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 +166,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,
);
}
diff --git a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php
index ff3c20c26d..e1da002700 100644
--- a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php
+++ b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php
@@ -39,7 +39,8 @@ class RouteTest extends \PHPUnit_Framework_TestCase
array('requirements', array('_method' => 'GET'), 'getRequirements'),
array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'),
array('name', 'blog_index', 'getName'),
- array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults')
+ array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'),
+ array('hostname_pattern', array('{locale}.example.com'), 'getHostnamePattern')
);
}
}
diff --git a/src/Symfony/Component/Routing/Tests/CompiledRouteTest.php b/src/Symfony/Component/Routing/Tests/CompiledRouteTest.php
index c65da0c6f2..215ebb707b 100644
--- a/src/Symfony/Component/Routing/Tests/CompiledRouteTest.php
+++ b/src/Symfony/Component/Routing/Tests/CompiledRouteTest.php
@@ -17,10 +17,10 @@ class CompiledRouteTest extends \PHPUnit_Framework_TestCase
{
public function testAccessors()
{
- $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array('variables'));
- $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its first argument');
- $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its second argument');
- $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its third argument');
- $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its forth argument');
+ $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), array(), array(), array(), array('variables'));
+ $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument');
+ $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument');
+ $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument');
+ $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument');
}
}
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 baa07b6d60..4b9c11d4d0 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache
@@ -68,8 +68,96 @@ RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz6,E=_ROUTING_default_foo:bar\
RewriteCond %{REQUEST_URI} ^/te\ st/baz$
RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz7]
+# baz8
+RewriteCond %{REQUEST_URI} ^/te\\\ st/baz$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz8]
+
+# baz9
+RewriteCond %{REQUEST_URI} ^/test/(te\\\ st)$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz9,E=_ROUTING_param_baz:%1]
+
+RewriteCond %{HTTP:Host} ^a\.example\.com$
+RewriteRule .? - [E=__ROUTING_hostname_1:1]
+
+# route1
+RewriteCond %{ENV:__ROUTING_hostname_1} =1
+RewriteCond %{REQUEST_URI} ^/route1$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route1]
+
+# route2
+RewriteCond %{ENV:__ROUTING_hostname_1} =1
+RewriteCond %{REQUEST_URI} ^/c2/route2$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route2]
+
+RewriteCond %{HTTP:Host} ^b\.example\.com$
+RewriteRule .? - [E=__ROUTING_hostname_2:1]
+
+# route3
+RewriteCond %{ENV:__ROUTING_hostname_2} =1
+RewriteCond %{REQUEST_URI} ^/c2/route3$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route3]
+
+RewriteCond %{HTTP:Host} ^a\.example\.com$
+RewriteRule .? - [E=__ROUTING_hostname_3:1]
+
+# route4
+RewriteCond %{ENV:__ROUTING_hostname_3} =1
+RewriteCond %{REQUEST_URI} ^/route4$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route4]
+
+RewriteCond %{HTTP:Host} ^c\.example\.com$
+RewriteRule .? - [E=__ROUTING_hostname_4:1]
+
+# route5
+RewriteCond %{ENV:__ROUTING_hostname_4} =1
+RewriteCond %{REQUEST_URI} ^/route5$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route5]
+
+# route6
+RewriteCond %{REQUEST_URI} ^/route6$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route6]
+
+RewriteCond %{HTTP:Host} ^([^\.]++)\.example\.com$
+RewriteRule .? - [E=__ROUTING_hostname_5:1,E=__ROUTING_hostname_5_var1:%1]
+
+# route11
+RewriteCond %{ENV:__ROUTING_hostname_5} =1
+RewriteCond %{REQUEST_URI} ^/route11$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route11,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1}]
+
+# route12
+RewriteCond %{ENV:__ROUTING_hostname_5} =1
+RewriteCond %{REQUEST_URI} ^/route12$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route12,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1},E=_ROUTING_default_var1:val]
+
+# route13
+RewriteCond %{ENV:__ROUTING_hostname_5} =1
+RewriteCond %{REQUEST_URI} ^/route13/([^/]++)$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route13,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1},E=_ROUTING_param_name:%1]
+
+# route14
+RewriteCond %{ENV:__ROUTING_hostname_5} =1
+RewriteCond %{REQUEST_URI} ^/route14/([^/]++)$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route14,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1},E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val]
+
+RewriteCond %{HTTP:Host} ^c\.example\.com$
+RewriteRule .? - [E=__ROUTING_hostname_6:1]
+
+# route15
+RewriteCond %{ENV:__ROUTING_hostname_6} =1
+RewriteCond %{REQUEST_URI} ^/route15/([^/]++)$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route15,E=_ROUTING_param_name:%1]
+
+# route16
+RewriteCond %{REQUEST_URI} ^/route16/([^/]++)$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route16,E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val]
+
+# route17
+RewriteCond %{REQUEST_URI} ^/route17$
+RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route17]
+
# 405 Method Not Allowed
-RewriteCond %{_ROUTING_allow_GET} !-z [OR]
-RewriteCond %{_ROUTING_allow_HEAD} !-z [OR]
-RewriteCond %{_ROUTING_allow_POST} !-z
+RewriteCond %{ENV:_ROUTING__allow_GET} =1 [OR]
+RewriteCond %{ENV:_ROUTING__allow_HEAD} =1 [OR]
+RewriteCond %{ENV:_ROUTING__allow_POST} =1
RewriteRule .* app.php [QSA,L]
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 9fafa7b3e1..19ed9ca13f 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
@@ -27,7 +27,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
// foo
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?baz|symfony)$#s', $pathinfo, $matches)) {
- return array_merge($this->mergeDefaults($matches, array ( 'def' => 'test',)), array('_route' => 'foo'));
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',));
}
if (0 === strpos($pathinfo, '/bar')) {
@@ -38,9 +38,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
goto not_bar;
}
- $matches['_route'] = 'bar';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ());
}
not_bar:
@@ -51,9 +49,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
goto not_barhead;
}
- $matches['_route'] = 'barhead';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ());
}
not_barhead:
@@ -80,9 +76,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
// baz4
if (preg_match('#^/test/(?[^/]++)/$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'baz4';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
}
// baz5
@@ -92,9 +86,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
goto not_baz5;
}
- $matches['_route'] = 'baz5';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ());
}
not_baz5:
@@ -105,9 +97,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
goto not_bazbaz6;
}
- $matches['_route'] = 'baz.baz6';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ());
}
not_bazbaz6:
@@ -120,9 +110,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
// quoter
if (preg_match('#^/(?[\']+)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'quoter';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ());
}
// space
@@ -134,40 +122,30 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo1
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo1';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ());
}
// bar1
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'bar1';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ());
}
}
// overridden
if (preg_match('#^/a/(?.*)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'overridden';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ());
}
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo2
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo2';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ());
}
// bar2
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'bar2';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ());
}
}
@@ -177,7 +155,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)) {
- return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld'));
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',));
}
// overridden2
@@ -194,34 +172,119 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
// foo3
if (preg_match('#^/(?<_locale>[^/]++)/b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo3';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ());
}
// bar3
if (preg_match('#^/(?<_locale>[^/]++)/b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'bar3';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ());
}
- if (0 === strpos($pathinfo, '/a')) {
- if (0 === strpos($pathinfo, '/aba')) {
- // ababa
- if ($pathinfo === '/ababa') {
- return array('_route' => 'ababa');
+ if (0 === strpos($pathinfo, '/aba')) {
+ // ababa
+ if ($pathinfo === '/ababa') {
+ return array('_route' => 'ababa');
+ }
+
+ // foo4
+ if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ());
+ }
+
+ }
+
+ $hostname = $this->context->getHost();
+
+ if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route1
+ if ($pathinfo === '/route1') {
+ return array('_route' => 'route1');
+ }
+
+ // route2
+ if ($pathinfo === '/c2/route2') {
+ return array('_route' => 'route2');
+ }
+
+ }
+
+ if (preg_match('#^b\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route3
+ if ($pathinfo === '/c2/route3') {
+ return array('_route' => 'route3');
+ }
+
+ }
+
+ if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route4
+ if ($pathinfo === '/route4') {
+ return array('_route' => 'route4');
+ }
+
+ }
+
+ if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route5
+ if ($pathinfo === '/route5') {
+ return array('_route' => 'route5');
+ }
+
+ }
+
+ // route6
+ if ($pathinfo === '/route6') {
+ return array('_route' => 'route6');
+ }
+
+ if (preg_match('#^(?[^\\.]++)\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ if (0 === strpos($pathinfo, '/route1')) {
+ // route11
+ if ($pathinfo === '/route11') {
+ return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route11')), array ());
}
- // foo4
- if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo4';
+ // route12
+ if ($pathinfo === '/route12') {
+ return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
+ }
- return $matches;
+ // route13
+ if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route13')), array ());
+ }
+
+ // route14
+ if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',));
}
}
+ }
+
+ if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route15
+ if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
+ }
+
+ }
+
+ if (0 === strpos($pathinfo, '/route1')) {
+ // route16
+ if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',));
+ }
+
+ // route17
+ if ($pathinfo === '/route17') {
+ return array('_route' => 'route17');
+ }
+
+ }
+
+ if (0 === strpos($pathinfo, '/a')) {
// a
if ($pathinfo === '/a/a...') {
return array('_route' => 'a');
@@ -230,16 +293,12 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
if (0 === strpos($pathinfo, '/a/b')) {
// b
if (preg_match('#^/a/b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'b';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ());
}
// c
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'c';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ());
}
}
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 7dcc8ccb28..6063042d0d 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
@@ -27,7 +27,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
// foo
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?baz|symfony)$#s', $pathinfo, $matches)) {
- return array_merge($this->mergeDefaults($matches, array ( 'def' => 'test',)), array('_route' => 'foo'));
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',));
}
if (0 === strpos($pathinfo, '/bar')) {
@@ -38,9 +38,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
goto not_bar;
}
- $matches['_route'] = 'bar';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ());
}
not_bar:
@@ -51,9 +49,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
goto not_barhead;
}
- $matches['_route'] = 'barhead';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ());
}
not_barhead:
@@ -88,9 +84,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
return $this->redirect($pathinfo.'/', 'baz4');
}
- $matches['_route'] = 'baz4';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
}
// baz5
@@ -100,9 +94,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
goto not_baz5;
}
- $matches['_route'] = 'baz5';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ());
}
not_baz5:
@@ -113,9 +105,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
goto not_bazbaz6;
}
- $matches['_route'] = 'baz.baz6';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ());
}
not_bazbaz6:
@@ -128,9 +118,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
// quoter
if (preg_match('#^/(?[\']+)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'quoter';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ());
}
// space
@@ -142,40 +130,30 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo1
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo1';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ());
}
// bar1
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'bar1';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ());
}
}
// overridden
if (preg_match('#^/a/(?.*)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'overridden';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ());
}
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo2
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo2';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ());
}
// bar2
if (preg_match('#^/a/b\'b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'bar2';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ());
}
}
@@ -185,7 +163,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)) {
- return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld'));
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',));
}
// overridden2
@@ -206,34 +184,119 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
// foo3
if (preg_match('#^/(?<_locale>[^/]++)/b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo3';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ());
}
// bar3
if (preg_match('#^/(?<_locale>[^/]++)/b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'bar3';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ());
}
- if (0 === strpos($pathinfo, '/a')) {
- if (0 === strpos($pathinfo, '/aba')) {
- // ababa
- if ($pathinfo === '/ababa') {
- return array('_route' => 'ababa');
+ if (0 === strpos($pathinfo, '/aba')) {
+ // ababa
+ if ($pathinfo === '/ababa') {
+ return array('_route' => 'ababa');
+ }
+
+ // foo4
+ if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ());
+ }
+
+ }
+
+ $hostname = $this->context->getHost();
+
+ if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route1
+ if ($pathinfo === '/route1') {
+ return array('_route' => 'route1');
+ }
+
+ // route2
+ if ($pathinfo === '/c2/route2') {
+ return array('_route' => 'route2');
+ }
+
+ }
+
+ if (preg_match('#^b\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route3
+ if ($pathinfo === '/c2/route3') {
+ return array('_route' => 'route3');
+ }
+
+ }
+
+ if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route4
+ if ($pathinfo === '/route4') {
+ return array('_route' => 'route4');
+ }
+
+ }
+
+ if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route5
+ if ($pathinfo === '/route5') {
+ return array('_route' => 'route5');
+ }
+
+ }
+
+ // route6
+ if ($pathinfo === '/route6') {
+ return array('_route' => 'route6');
+ }
+
+ if (preg_match('#^(?[^\\.]++)\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ if (0 === strpos($pathinfo, '/route1')) {
+ // route11
+ if ($pathinfo === '/route11') {
+ return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route11')), array ());
}
- // foo4
- if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'foo4';
+ // route12
+ if ($pathinfo === '/route12') {
+ return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
+ }
- return $matches;
+ // route13
+ if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route13')), array ());
+ }
+
+ // route14
+ if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',));
}
}
+ }
+
+ if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
+ // route15
+ if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
+ }
+
+ }
+
+ if (0 === strpos($pathinfo, '/route1')) {
+ // route16
+ if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?[^/]++)$#s', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',));
+ }
+
+ // route17
+ if ($pathinfo === '/route17') {
+ return array('_route' => 'route17');
+ }
+
+ }
+
+ if (0 === strpos($pathinfo, '/a')) {
// a
if ($pathinfo === '/a/a...') {
return array('_route' => 'a');
@@ -242,16 +305,12 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if (0 === strpos($pathinfo, '/a/b')) {
// b
if (preg_match('#^/a/b/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'b';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ());
}
// c
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'c';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ());
}
}
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 a0b1320a76..7dbdffef85 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
@@ -33,9 +33,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
// dynamic
if (preg_match('#^/rootprefix/(?[^/]++)$#s', $pathinfo, $matches)) {
- $matches['_route'] = 'dynamic';
-
- return $matches;
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ());
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php
index 0d1d3a73da..08e7d6e347 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php
@@ -6,8 +6,9 @@ $collection = new RouteCollection();
$collection->add('blog_show', new Route(
'/blog/{slug}',
array('_controller' => 'MyBlogBundle:Blog:show'),
- array(),
- array('compiler_class' => 'RouteCompiler')
+ array('_method' => 'GET', 'locale' => '\w+'),
+ array('compiler_class' => 'RouteCompiler'),
+ '{locale}.example.com'
));
return $collection;
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml
index fdb38073a0..079116399c 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml
@@ -4,9 +4,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
-
+
MyBundle:Blog:show
GET
+ \w+
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml
index d5136cbab8..8fad8a0923 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml
@@ -1,5 +1,7 @@
blog_show:
- pattern: /blog/{slug}
- defaults: { _controller: MyBlogBundle:Blog:show }
+ pattern: /blog/{slug}
+ defaults: { _controller: MyBlogBundle:Blog:show }
+ hostname_pattern: "{locale}.example.com"
+ requirements: { '_method': 'GET', 'locale': '\w+' }
options:
compiler_class: RouteCompiler
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml
index 14ac0fc331..b3747864fb 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml
@@ -4,8 +4,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
-
- foo
+
+ 123
\d+
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml
index e66f37613b..35a45398ff 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml
@@ -1,6 +1,7 @@
blog_show:
- resource: validpattern.yml
- prefix: /{foo}
- defaults: { 'foo': 'foo' }
- requirements: { 'foo': '\d+' }
- options: { 'foo': 'bar' }
+ resource: validpattern.yml
+ prefix: /{foo}
+ defaults: { 'foo': '123' }
+ requirements: { 'foo': '\d+' }
+ options: { 'foo': 'bar' }
+ hostname_pattern: "{locale}.example.com"
diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
index 210239a0d8..4319144b2f 100644
--- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
+++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
@@ -235,7 +235,7 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+')));
$this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true);
}
-
+
/**
* @expectedException Symfony\Component\Routing\Exception\InvalidParameterException
*/
@@ -262,7 +262,7 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
$routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'http')));
$this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
}
-
+
public function testPathWithTwoStartingSlashes()
{
$routes = $this->getRoutes('test', new Route('//path-and-not-domain'));
@@ -377,6 +377,62 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
$this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html'));
}
+ public function testWithHostnameDifferentFromContext()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
+
+ $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' =>'Fabien', 'locale' => 'fr')));
+ }
+
+ public function testWithHostnameSameAsContext()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
+
+ $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' =>'Fabien', 'locale' => 'fr')));
+ }
+
+ public function testWithHostnameSameAsContextAndAbsolute()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
+
+ $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' =>'Fabien', 'locale' => 'fr'), true));
+ }
+
+ /**
+ * @expectedException Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testUrlWithInvalidParameterInHostname()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false);
+ }
+
+ /**
+ * @expectedException Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testUrlWithInvalidParameterInHostnameWhenParamHasADefaultValue()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false);
+ }
+
+ /**
+ * @expectedException Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testUrlWithInvalidParameterEqualsDefaultValueInHostname()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false);
+ }
+
+ public function testUrlWithInvalidParameterInHostnameInNonStrictMode()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $generator = $this->getGenerator($routes);
+ $generator->setStrictRequirements(false);
+ $this->assertNull($generator->generate('test', array('foo' => 'baz'), false));
+ }
+
protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null)
{
$context = new RequestContext('/app.php');
diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
index 74c38737f2..0ea7b32e17 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
@@ -47,6 +47,10 @@ class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($routes), 'One route is loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
$route = $routes['blog_show'];
+ $this->assertEquals('/blog/{slug}', $route->getPattern());
+ $this->assertEquals('MyBlogBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertEquals('GET', $route->getRequirement('_method'));
+ $this->assertEquals('{locale}.example.com', $route->getHostnamePattern());
$this->assertEquals('RouteCompiler', $route->getOption('compiler_class'));
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
index e6f217606d..235a9d13af 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
@@ -48,6 +48,11 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($routes), 'One route is loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
$route = $routes['blog_show'];
+ $this->assertEquals('/blog/{slug}', $route->getPattern());
+ $this->assertEquals('MyBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertEquals('GET', $route->getRequirement('_method'));
+ $this->assertEquals('\w+', $route->getRequirement('locale'));
+ $this->assertEquals('{locale}.example.com', $route->getHostnamePattern());
$this->assertEquals('RouteCompiler', $route->getOption('compiler_class'));
}
@@ -59,9 +64,12 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($routes), 'One route is loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
- $this->assertEquals('foo', $routes['blog_show']->getDefault('foo'));
+ $this->assertEquals('/{foo}/blog/{slug}', $routes['blog_show']->getPattern());
+ $this->assertEquals('MyBundle:Blog:show', $routes['blog_show']->getDefault('_controller'));
+ $this->assertEquals('123', $routes['blog_show']->getDefault('foo'));
$this->assertEquals('\d+', $routes['blog_show']->getRequirement('foo'));
$this->assertEquals('bar', $routes['blog_show']->getOption('foo'));
+ $this->assertEquals('{locale}.example.com', $routes['blog_show']->getHostnamePattern());
}
/**
diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
index 0c870e667a..2606062a94 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
@@ -88,6 +88,11 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($routes), 'One route is loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
$route = $routes['blog_show'];
+ $this->assertEquals('/blog/{slug}', $route->getPattern());
+ $this->assertEquals('MyBlogBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertEquals('GET', $route->getRequirement('_method'));
+ $this->assertEquals('\w+', $route->getRequirement('locale'));
+ $this->assertEquals('{locale}.example.com', $route->getHostnamePattern());
$this->assertEquals('RouteCompiler', $route->getOption('compiler_class'));
}
@@ -99,9 +104,12 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($routes), 'One route is loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
- $this->assertEquals('foo', $routes['blog_show']->getDefault('foo'));
+ $this->assertEquals('/{foo}/blog/{slug}', $routes['blog_show']->getPattern());
+ $this->assertEquals('MyBlogBundle:Blog:show', $routes['blog_show']->getDefault('_controller'));
+ $this->assertEquals('123', $routes['blog_show']->getDefault('foo'));
$this->assertEquals('\d+', $routes['blog_show']->getRequirement('foo'));
$this->assertEquals('bar', $routes['blog_show']->getOption('foo'));
+ $this->assertEquals('{locale}.example.com', $routes['blog_show']->getHostnamePattern());
}
/**
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php
index 3161f3ce69..6550911eb7 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php
@@ -118,6 +118,20 @@ class ApacheUrlMatcherTest extends \PHPUnit_Framework_TestCase
'_route' => 'hello',
),
),
+ array(
+ 'REDIRECT_REDIRECT_ envs',
+ '/hello/world',
+ array(
+ 'REDIRECT_REDIRECT__ROUTING_route' => 'hello',
+ 'REDIRECT_REDIRECT__ROUTING_param__controller' => 'AcmeBundle:Default:index',
+ 'REDIRECT_REDIRECT__ROUTING_param_name' => 'world',
+ ),
+ array(
+ '_controller' => 'AcmeBundle:Default:index',
+ 'name' => 'world',
+ '_route' => 'hello',
+ ),
+ ),
);
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php
index 2847564b90..e72976f047 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php
@@ -124,6 +124,71 @@ class ApacheMatcherDumperTest extends \PHPUnit_Framework_TestCase
$collection->add('baz7', new Route(
'/te st/baz'
));
+ // space preceded with \ in path
+ $collection->add('baz8', new Route(
+ '/te\\ st/baz'
+ ));
+ // space preceded with \ in requirement
+ $collection->add('baz9', new Route(
+ '/test/{baz}',
+ array(),
+ array(
+ 'baz' => 'te\\\\ st',
+ )
+ ));
+
+ $collection1 = new RouteCollection();
+
+ $route1 = new Route('/route1', array(), array(), array(), 'a.example.com');
+ $collection1->add('route1', $route1);
+
+ $collection2 = new RouteCollection();
+
+ $route2 = new Route('/route2', array(), array(), array(), 'a.example.com');
+ $collection2->add('route2', $route2);
+
+ $route3 = new Route('/route3', array(), array(), array(), 'b.example.com');
+ $collection2->add('route3', $route3);
+
+ $collection1->addCollection($collection2, '/c2');
+
+ $route4 = new Route('/route4', array(), array(), array(), 'a.example.com');
+ $collection1->add('route4', $route4);
+
+ $route5 = new Route('/route5', array(), array(), array(), 'c.example.com');
+ $collection1->add('route5', $route5);
+
+ $route6 = new Route('/route6', array(), array(), array(), null);
+ $collection1->add('route6', $route6);
+
+ $collection->addCollection($collection1);
+
+ // hostname and variables
+
+ $collection1 = new RouteCollection();
+
+ $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route11', $route11);
+
+ $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route12', $route12);
+
+ $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route13', $route13);
+
+ $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route14', $route14);
+
+ $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com');
+ $collection1->add('route15', $route15);
+
+ $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null);
+ $collection1->add('route16', $route16);
+
+ $route17 = new Route('/route17', array(), array(), array(), null);
+ $collection1->add('route17', $route17);
+
+ $collection->addCollection($collection1);
return $collection;
}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php
new file mode 100644
index 0000000000..62ca947848
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php
@@ -0,0 +1,24 @@
+add($b);
+
+ $c = new DumperCollection();
+ $b->add($c);
+
+ $d = new DumperCollection();
+ $c->add($d);
+
+ $this->assertSame($a, $c->getRoot());
+ }
+}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php
index 917d4b16bb..9ba140bf8c 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php
@@ -112,4 +112,3 @@ EOF;
return $string;
}
}
-
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
index ee113851cd..9c15be5642 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
@@ -40,7 +40,6 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
$basePath = __DIR__.'/../../Fixtures/dumper/';
$dumper = new PhpMatcherDumper($collection);
-
$this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.');
}
@@ -156,6 +155,59 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
$collection1->add('foo4', new Route('/{foo}'));
$collection->addCollection($collection1, '/aba');
+ // prefix and hostname
+
+ $collection1 = new RouteCollection();
+
+ $route1 = new Route('/route1', array(), array(), array(), 'a.example.com');
+ $collection1->add('route1', $route1);
+
+ $collection2 = new RouteCollection();
+
+ $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com');
+ $collection1->add('route2', $route2);
+
+ $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com');
+ $collection1->add('route3', $route3);
+
+ $route4 = new Route('/route4', array(), array(), array(), 'a.example.com');
+ $collection1->add('route4', $route4);
+
+ $route5 = new Route('/route5', array(), array(), array(), 'c.example.com');
+ $collection1->add('route5', $route5);
+
+ $route6 = new Route('/route6', array(), array(), array(), null);
+ $collection1->add('route6', $route6);
+
+ $collection->addCollection($collection1);
+
+ // hostname and variables
+
+ $collection1 = new RouteCollection();
+
+ $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route11', $route11);
+
+ $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route12', $route12);
+
+ $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route13', $route13);
+
+ $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route14', $route14);
+
+ $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com');
+ $collection1->add('route15', $route15);
+
+ $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null);
+ $collection1->add('route16', $route16);
+
+ $route17 = new Route('/route17', array(), array(), array(), null);
+ $collection1->add('route17', $route17);
+
+ $collection->addCollection($collection1);
+
// multiple sub-collections with a single route and a prefix each
$collection1 = new RouteCollection();
$collection1->add('a', new Route('/a...'));
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
index c5e21fb05d..8105528885 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
@@ -328,4 +328,39 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase
$matcher = new UrlMatcher($coll, new RequestContext());
$this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523'));
}
+
+ public function testWithHostname()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com'));
+
+ $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar'));
+ }
+
+ public function testWithHostnameOnRouteCollection()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}'));
+ $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net'));
+ $coll->setHostnamePattern('{locale}.example.com');
+
+ $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar'));
+
+ $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar'));
+ }
+
+ /**
+ * @expectedException Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testWithOutHostnameHostnameDoesNotMatch()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com'));
+
+ $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com'));
+ $matcher->match('/foo/bar');
+ }
}
diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php
index 14584e9cc1..16d193e34b 100644
--- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php
+++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php
@@ -297,4 +297,18 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($rootCollection_A, $rootCollection_B);
}
+
+ public function testSetHostnamePattern()
+ {
+ $collection = new RouteCollection();
+ $routea = new Route('/a');
+ $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net');
+ $collection->add('a', $routea);
+ $collection->add('b', $routeb);
+
+ $collection->setHostnamePattern('{locale}.example.com');
+
+ $this->assertEquals('{locale}.example.com', $routea->getHostnamePattern());
+ $this->assertEquals('{locale}.example.com', $routeb->getHostnamePattern());
+ }
}
diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php
index 1c87ca3257..40395e175e 100644
--- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php
+++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php
@@ -179,4 +179,75 @@ 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/(?[^/]++)$#s', array('tld', 'name'), array('name'), array(
+ array('variable', '/', '[^/]++', 'name'),
+ array('text', '/hello'),
+ ),
+ '#^www\.example\.(?[^\.]++)$#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'),
+ ),
+ '#^(?[^\.]++)\.example\.(?[^\.]++)$#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'),
+ ),
+ '#^(?[^\.]++)\.example\.(?[^\.]++)$#s', array('locale', 'tld'), array(
+ array('variable', '.', '[^\.]++', 'tld'),
+ array('text', '.example'),
+ array('variable', '', '[^\.]++', 'locale'),
+ ),
+ ),
+ );
+ }
}
diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php
index af5df60bdc..1a11d7440e 100644
--- a/src/Symfony/Component/Routing/Tests/RouteTest.php
+++ b/src/Symfony/Component/Routing/Tests/RouteTest.php
@@ -17,11 +17,12 @@ class RouteTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
- $route = new Route('/{foo}', array('foo' => 'bar'), array('foo' => '\d+'), array('foo' => 'bar'));
+ $route = new Route('/{foo}', array('foo' => 'bar'), array('foo' => '\d+'), array('foo' => 'bar'), '{locale}.example.com');
$this->assertEquals('/{foo}', $route->getPattern(), '__construct() takes a pattern as its first argument');
$this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '__construct() takes defaults as its second argument');
$this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '__construct() takes requirements as its third argument');
$this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument');
+ $this->assertEquals('{locale}.example.com', $route->getHostnamePattern(), '__construct() takes a hostname pattern as its fifth argument');
}
public function testPattern()
@@ -121,6 +122,13 @@ class RouteTest extends \PHPUnit_Framework_TestCase
);
}
+ public function testHostnamePattern()
+ {
+ $route = new Route('/');
+ $route->setHostnamePattern('{locale}.example.net');
+ $this->assertEquals('{locale}.example.net', $route->getHostnamePattern(), '->setHostnamePattern() sets the hostname pattern');
+ }
+
public function testCompile()
{
$route = new Route('/{foo}');