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}');