From 23feb37a982d97da25e2af96515bd474ab0207bc Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:13:37 +0200 Subject: [PATCH 01/28] [Routing] added hostname matching support to CompiledRoute --- .../Component/Routing/CompiledRoute.php | 63 +++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index 54d4e2ce55..d5e5fe3ef8 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -22,21 +22,33 @@ 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 $variables An array of variables + * @param array $pathVariables An array of path variables + * @param array $hostnameVariables An array of hostname variables + * @param array $hostnameRegex Hostname regex + * @param array $hostnameTokens Hostname tokens */ - public function __construct($staticPrefix, $regex, array $tokens, array $variables) + public function __construct($staticPrefix, $regex, array $tokens, array $variables, array $pathVariables = array(), array $hostnameVariables = array(), $hostnameRegex = null, array $hostnameTokens = array()) { $this->staticPrefix = $staticPrefix; $this->regex = $regex; $this->tokens = $tokens; $this->variables = $variables; + $this->pathVariables = $pathVariables; + $this->hostnameVariables = $hostnameVariables; + $this->hostnameRegex = $hostnameRegex; + $this->hostnameTokens = $hostnameTokens; } /** @@ -59,6 +71,16 @@ class CompiledRoute return $this->regex; } + /** + * Returns the hostname regex + * + * @return string The hostname regex + */ + 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; + } + } From add36580010347deddbd7f987e3589f484be65fe Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:13:54 +0200 Subject: [PATCH 02/28] [Routing] added hostname matching support to Route and RouteCollection --- src/Symfony/Component/Routing/Route.php | 17 ++++++++++- .../Component/Routing/RouteCollection.php | 29 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php index 775be741d3..bbd882f09d 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 = null) { $this->setPattern($pattern); $this->setDefaults($defaults); $this->setRequirements($requirements); $this->setOptions($options); + $this->setHostnamePattern($hostnamePattern); } public function __clone() @@ -103,6 +106,18 @@ class Route implements \Serializable return $this; } + public function getHostnamePattern() + { + return $this->hostnamePattern; + } + + public function setHostnamePattern($pattern) + { + $this->hostnamePattern = $pattern; + + return $this; + } + /** * Returns the options. * diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 17be00e51e..bae7b4dd6f 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 = null; } public function __clone() @@ -188,12 +190,13 @@ class RouteCollection implements \IteratorAggregate, \Countable * @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 = null) { // prevent infinite loops by recursive referencing $root = $this->getRoot(); @@ -208,6 +211,12 @@ 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); + + // Allow child collection to have a different pattern + if (!$collection->getHostnamePattern()) { + $collection->setHostnamePattern($hostnamePattern); + } + $this->routes[] = $collection; } @@ -341,4 +350,22 @@ class RouteCollection implements \IteratorAggregate, \Countable return false; } + + public function getHostnamePattern() + { + return $this->hostnamePattern; + } + + public function setHostnamePattern($pattern) + { + $this->hostnamePattern = $pattern; + + foreach ($this->routes as $name => $route) { + // Allow individual routes to have a different pattern + if (!$route->getHostnamePattern()) { + $route->setHostnamePattern($pattern); + } + } + } + } From 402359ba9d65c63a7191949e05b231a7dd51fca6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:15:20 +0200 Subject: [PATCH 03/28] [Routing] added hostname matching support to RouteCompiler --- .../Component/Routing/RouteCompiler.php | 77 ++++++++++++++++--- .../Routing/Tests/RouteCompilerTest.php | 74 ++++++++++++++++++ 2 files changed, 139 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 03d3e38644..418303d138 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -37,11 +37,58 @@ class RouteCompiler implements RouteCompilerInterface */ public function compile(Route $route) { + $staticPrefix = null; + $hostnameVariables = array(); + $pathVariables = array(); + $variables = array(); + $tokens = array(); + $regex = null; + $hostnameRegex = null; + $hostnameTokens = array(); + + if (null !== $hostnamePattern = $route->getHostnamePattern()) { + + $result = $this->compilePattern($route, $hostnamePattern, true); + + $hostnameVariables = $result['variables']; + $variables = array_merge($variables, $hostnameVariables); + + $hostnameTokens = $result['tokens']; + $hostnameRegex = $result['regex']; + } + $pattern = $route->getPattern(); + + $result = $this->compilePattern($route, $pattern, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + array_unique($variables), + $pathVariables, + $hostnameVariables, + $hostnameRegex, + $hostnameTokens + ); + } + + private function compilePattern(Route $route, $pattern, $isHostname) + { + $len = strlen($pattern); $tokens = array(); $variables = array(); $matches = array(); $pos = 0; + $defaultSeparator = $isHostname ? '.' : '/'; // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. @@ -78,7 +125,11 @@ class RouteCompiler implements RouteCompilerInterface // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. $nextSeparator = $this->findNextSeparator($followingPattern); - $regexp = sprintf('[^/%s]+', '/' !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : ''); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. @@ -99,12 +150,14 @@ class RouteCompiler implements RouteCompilerInterface // find the first optional token $firstOptional = INF; - for ($i = count($tokens) - 1; $i >= 0; $i--) { - $token = $tokens[$i]; - if ('variable' === $token[0] && $route->hasDefault($token[3])) { - $firstOptional = $i; - } else { - break; + if (!$isHostname) { + for ($i = count($tokens) - 1; $i >= 0; $i--) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } } } @@ -114,11 +167,11 @@ class RouteCompiler implements RouteCompilerInterface $regexp .= $this->computeRegexp($tokens, $i, $firstOptional); } - return new CompiledRoute( - 'text' === $tokens[0][0] ? $tokens[0][1] : '', - self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s', - array_reverse($tokens), - $variables + return array( + 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', + 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s', + 'tokens' => array_reverse($tokens), + 'variables' => $variables, ); } diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index 1c87ca3257..d8827df58b 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -179,4 +179,78 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array('1e2') ); } + + /** + * @dataProvider provideCompileWithHostnameData + */ + public function testCompileWithHostname($name, $arguments, $prefix, $regex, $variables, $pathVariables, $tokens, $hostnameRegex, $hostnameVariables, $hostnameTokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($pathVariables, $compiled->getPathVariables(), $name.' (path variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + + + $this->assertEquals($hostnameRegex, str_replace(array("\n", ' '), '', $compiled->getHostnameRegex()), $name.' (hostname regex)'); + $this->assertEquals($hostnameVariables, $compiled->getHostnameVariables(), $name.' (hostname variables)'); + $this->assertEquals($hostnameTokens, $compiled->getHostnameTokens(), $name.' (hostname tokens)'); + } + + public function provideCompileWithHostnameData() + { + return array( + array( + 'Route with hostname pattern', + array('/hello', array(), array(), array(), 'www.example.com'), + '/hello', '#^/hello$#s', array(), array(), array( + array('text', '/hello'), + ), + '#^www\.example\.com$#s', array(), array( + array('text', 'www.example.com'), + ), + ), + array( + 'Route with hostname pattern and some variables', + array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'), + '/hello', '#^/hello/(?[^/]++)$#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'), + ), + ), + ); + } } + From 85d11af880e4a6525da66f99c59aed9d0aa89e20 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:18:29 +0200 Subject: [PATCH 04/28] [Routing] added hostname matching support to PhpMatcherDumper --- .../Matcher/Dumper/DumperCollection.php | 48 +++++++ .../Matcher/Dumper/PhpMatcherDumper.php | 100 +++++++++++++-- .../Tests/Fixtures/dumper/url_matcher1.php | 117 ++++++++++++++++-- .../Tests/Fixtures/dumper/url_matcher2.php | 117 ++++++++++++++++-- .../Matcher/Dumper/DumperCollectionTest.php | 25 ++++ .../Matcher/Dumper/PhpMatcherDumperTest.php | 57 +++++++++ 6 files changed, 437 insertions(+), 27 deletions(-) create mode 100644 src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php index c75e9a0be8..cab4ad27c0 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php @@ -98,4 +98,52 @@ 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) + { + if ($this->hasAttribute($name)) { + return $this->attributes[$name]; + } else { + return $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..f245818998 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -109,10 +109,37 @@ 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; } /** @@ -171,6 +198,7 @@ EOF; $conditions = array(); $hasTrailingSlash = false; $matches = false; + $hostnameMatches = false; $methods = array(); if ($req = $route->getRequirement('_method')) { @@ -183,7 +211,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 +233,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) { + if (($matches || $hostnameMatches) && $route->getDefaults()) { + + $vars = array(); + if ($matches) { + $vars[] = '$matches'; + } + if ($hostnameMatches) { + $vars[] = '$hostnameMatches'; + } + $matchesExpr = implode(' + ', $vars); + + $code .= sprintf(" return array_merge(\$this->mergeDefaults(%s, %s), array('_route' => '%s'));\n" + , $matchesExpr, str_replace("\n", '', var_export($route->getDefaults(), true)), $name); + + } elseif ($matches || $hostnameMatches) { + + if (!$matches) { + $code .= " \$matches = \$hostnameMatches;\n"; + } else { + if ($hostnameMatches) { + $code .= " \$matches = \$matches + \$hostnameMatches;\n"; + } + } + $code .= sprintf(" \$matches['_route'] = '%s';\n\n", $name); $code .= " return \$matches;\n"; } elseif ($route->getDefaults()) { @@ -308,6 +360,37 @@ EOF; return $to; } + /** + * Groups consecutive routes having the same hostname regex + * + * The results is a collection of collections of routes having the same + * hostnameRegex + * + * @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. * @@ -331,5 +414,4 @@ EOF; return $tree; } - } 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..8111e41715 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -206,22 +206,121 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher return $matches; } - 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)) { - $matches['_route'] = 'foo4'; + // foo4 + if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) { + $matches['_route'] = 'foo4'; + + return $matches; + } + + } + + $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') { + $matches = $hostnameMatches; + $matches['_route'] = 'route11'; return $matches; } + // route12 + if ($pathinfo === '/route12') { + return array_merge($this->mergeDefaults($hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route12')); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { + $matches = $matches + $hostnameMatches; + $matches['_route'] = 'route13'; + + return $matches; + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) { + return array_merge($this->mergeDefaults($matches + $hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route14')); + } + } + } + + if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?[^/]++)$#s', $pathinfo, $matches)) { + $matches['_route'] = 'route15'; + + return $matches; + } + + } + + if (0 === strpos($pathinfo, '/route1')) { + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?[^/]++)$#s', $pathinfo, $matches)) { + return array_merge($this->mergeDefaults($matches, array ( 'var1' => 'val',)), array('_route' => 'route16')); + } + + // route17 + if ($pathinfo === '/route17') { + return array('_route' => 'route17'); + } + + } + + if (0 === strpos($pathinfo, '/a')) { // a if ($pathinfo === '/a/a...') { return array('_route' => 'a'); 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..16040bc29c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -218,22 +218,121 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec return $matches; } - 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)) { - $matches['_route'] = 'foo4'; + // foo4 + if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) { + $matches['_route'] = 'foo4'; + + return $matches; + } + + } + + $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') { + $matches = $hostnameMatches; + $matches['_route'] = 'route11'; return $matches; } + // route12 + if ($pathinfo === '/route12') { + return array_merge($this->mergeDefaults($hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route12')); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { + $matches = $matches + $hostnameMatches; + $matches['_route'] = 'route13'; + + return $matches; + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) { + return array_merge($this->mergeDefaults($matches + $hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route14')); + } + } + } + + if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?[^/]++)$#s', $pathinfo, $matches)) { + $matches['_route'] = 'route15'; + + return $matches; + } + + } + + if (0 === strpos($pathinfo, '/route1')) { + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?[^/]++)$#s', $pathinfo, $matches)) { + return array_merge($this->mergeDefaults($matches, array ( 'var1' => 'val',)), array('_route' => 'route16')); + } + + // route17 + if ($pathinfo === '/route17') { + return array('_route' => 'route17'); + } + + } + + if (0 === strpos($pathinfo, '/a')) { // a if ($pathinfo === '/a/a...') { return array('_route' => 'a'); 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..7f2d914424 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php @@ -0,0 +1,25 @@ +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/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php index ee113851cd..aec0a9148f 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Routing\Tests\Matcher\Dumper; use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase { @@ -41,6 +42,7 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase $dumper = new PhpMatcherDumper($collection); + file_put_contents('/tmp/' . $fixture, $dumper->dump($options)); $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); } @@ -156,6 +158,61 @@ 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('/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); + // multiple sub-collections with a single route and a prefix each $collection1 = new RouteCollection(); $collection1->add('a', new Route('/a...')); From cab450c94ecc2d6fdcf615c53657ac3c1d6792ad Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:19:16 +0200 Subject: [PATCH 05/28] [Routing] added hostname matching support to YamlFileLoader --- src/Symfony/Component/Routing/Loader/YamlFileLoader.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index d6d0539b17..e75dc202b6 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'] : null; $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); } From 7a15e006e62ea06e3ba3374d99a7c60c02070abe Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:20:04 +0200 Subject: [PATCH 06/28] [Routing] added hostname matching support to AnnotationClassLoader --- .../Component/Routing/Annotation/Route.php | 11 ++++++++++ .../Routing/Loader/AnnotationClassLoader.php | 20 ++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index f60af463f2..84c15fc2f7 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. @@ -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/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 3ef006b057..76181df9e9 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -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' => null, ); $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(); @@ -157,7 +162,12 @@ abstract class AnnotationClassLoader implements LoaderInterface $requirements = array_merge($globals['requirements'], $annot->getRequirements()); $options = array_merge($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); From 805806a1e8c653c5603a03b1e7af42730de059e7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:20:48 +0200 Subject: [PATCH 07/28] [Routing] added hostname matching support to UrlGenerator --- .../Generator/Dumper/PhpGeneratorDumper.php | 5 +-- .../Routing/Generator/UrlGenerator.php | 34 +++++++++++++++++-- .../Dumper/PhpGeneratorDumperTest.php | 1 + 3 files changed, 35 insertions(+), 5 deletions(-) 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..e16b07972b 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); @@ -185,6 +185,25 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $url = substr($url, 0, -1) . '%2E'; } + if ($hostnameTokens) { + $host = ''; + foreach ($hostnameTokens as $token) { + if ('variable' === $token[0]) { + if (in_array($tparams[$token[3]], array(null, '', false), true)) { + // check requirement + if ($tparams[$token[3]] && !preg_match('#^'.$token[2].'$#', $tparams[$token[3]])) { + throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]])); + } + } + + $host = $token[1].$tparams[$token[3]].$host; + + } elseif ('text' === $token[0]) { + $host = $token[1].$host; + } + } + } + // add a query string if needed $extra = array_diff_key($parameters, $variables); if ($extra && $query = http_build_query($extra, '', '&')) { @@ -198,7 +217,16 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $scheme = $req; } + if ($hostnameTokens) { + $absolute = true; + } + if ($absolute) { + + if (!$hostnameTokens) { + $host = $this->context->getHost(); + } + $port = ''; if ('http' === $scheme && 80 != $this->context->getHttpPort()) { $port = ':'.$this->context->getHttpPort(); @@ -206,7 +234,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/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php index 6a86736abb..dee68b1957 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; class PhpGeneratorDumperTest extends \PHPUnit_Framework_TestCase { From 462999d2d2b3e667e07f341ac69fdf8bf3f4685f Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 14 Apr 2012 19:21:10 +0200 Subject: [PATCH 08/28] [Routing] display hostname pattern in router:debug output --- .../Command/RouterDebugCommand.php | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 1bc4d0d463..b225857553 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -82,8 +82,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 +93,18 @@ EOF ? implode(', ', $requirements['_method']) : $requirements['_method'] ) : 'ANY'; + $hostname = null !== $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 +112,9 @@ EOF ? implode(', ', $requirements['_method']) : $requirements['_method'] ) : 'ANY'; - $output->writeln(sprintf($format, $name, $method, $route->getPattern())); + $hostname = null !== $route->getHostnamePattern() + ? $route->getHostnamePattern() : 'ANY'; + $output->writeln(sprintf($format, $name, $method, $hostname, $route->getPattern())); } } From fc015d522759316bbf47bd38cd94f198d96dccc9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 24 Apr 2012 22:58:11 +0200 Subject: [PATCH 09/28] [Routing] fixed route generation with a hostname pattern when the hostname is the same as the current one (no need to force the generated URL to be absolute) --- .../Routing/Generator/UrlGenerator.php | 47 +++++++++---------- .../Tests/Generator/UrlGeneratorTest.php | 21 +++++++++ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index e16b07972b..5cf7dbdcc0 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -185,32 +185,13 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $url = substr($url, 0, -1) . '%2E'; } - if ($hostnameTokens) { - $host = ''; - foreach ($hostnameTokens as $token) { - if ('variable' === $token[0]) { - if (in_array($tparams[$token[3]], array(null, '', false), true)) { - // check requirement - if ($tparams[$token[3]] && !preg_match('#^'.$token[2].'$#', $tparams[$token[3]])) { - throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]])); - } - } - - $host = $token[1].$tparams[$token[3]].$host; - - } elseif ('text' === $token[0]) { - $host = $token[1].$host; - } - } - } - // add a query string if needed $extra = array_diff_key($parameters, $variables); if ($extra && $query = http_build_query($extra, '', '&')) { $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; @@ -218,15 +199,29 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt } if ($hostnameTokens) { - $absolute = true; + $ghost = ''; + foreach ($hostnameTokens as $token) { + if ('variable' === $token[0]) { + if (in_array($mergedParams[$token[3]], array(null, '', false), true)) { + // check requirement + if ($mergedParams[$token[3]] && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { + throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $mergedParams[$token[3]])); + } + } + + $ghost = $token[1].$mergedParams[$token[3]].$ghost; + } elseif ('text' === $token[0]) { + $ghost = $token[1].$ghost; + } + } + + if ($ghost != $host) { + $host = $ghost; + $absolute = true; + } } if ($absolute) { - - if (!$hostnameTokens) { - $host = $this->context->getHost(); - } - $port = ''; if ('http' === $scheme && 80 != $this->context->getHttpPort()) { $port = ':'.$this->context->getHttpPort(); diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 210239a0d8..080125ce11 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -377,6 +377,27 @@ 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)); + } + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) { $context = new RequestContext('/app.php'); From 11b43782385b2631a17bef3a24349a6c02e14ec9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:07:09 +0200 Subject: [PATCH 10/28] [Routing] added hostname support in UrlMatcher --- .../Component/Routing/Matcher/UrlMatcher.php | 7 +++- .../Component/Routing/RouteCollection.php | 1 - .../Routing/Tests/Matcher/UrlMatcherTest.php | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 8e757bf3d7..3c0db6dd7b 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -117,6 +117,11 @@ class UrlMatcher implements UrlMatcherInterface if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { continue; } + $hostnameMatches = array(); + + if ($compiledRoute->getHostnameRegex() && !preg_match($compiledRoute->getHostnameRegex(), $this->context->getHost(), $hostnameMatches)) { + continue; + } // check HTTP method requirement if ($req = $route->getRequirement('_method')) { @@ -142,7 +147,7 @@ class UrlMatcher implements UrlMatcherInterface continue; } - return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name)); + return array_merge($this->mergeDefaults($hostnameMatches + $matches, $route->getDefaults()), array('_route' => $name)); } } diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index bae7b4dd6f..ae2755205f 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -367,5 +367,4 @@ class RouteCollection implements \IteratorAggregate, \Countable } } } - } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index c5e21fb05d..6e1f804810 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.net')); + $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'); + } } From 62de881ddbc729dcd9b765a4a520de166ecff561 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:11:35 +0200 Subject: [PATCH 11/28] [Routing] clarified a variable content --- src/Symfony/Component/Routing/CompiledRoute.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index d5e5fe3ef8..b6a7995566 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -33,7 +33,7 @@ class CompiledRoute * @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 array $variables An array of variables (variables defined in the path and in the hostname patterns) * @param array $pathVariables An array of path variables * @param array $hostnameVariables An array of hostname variables * @param array $hostnameRegex Hostname regex From d91e5a26a27cc6d3575075beb4f573177a715957 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:14:38 +0200 Subject: [PATCH 12/28] [Routing] fixed Route annotation for hostname (should be hostname_pattern instead of hostnamePattern) --- src/Symfony/Component/Routing/Annotation/Route.php | 2 +- src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index 84c15fc2f7..e593f97ff6 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -44,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))); } 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') ); } } From 92f9c1561b89b5bb2754257b41a5fa28f32ab990 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:25:08 +0200 Subject: [PATCH 13/28] [Routing] changed CompiledRoute signature to be more consistent --- src/Symfony/Component/Routing/CompiledRoute.php | 10 +++++----- src/Symfony/Component/Routing/RouteCompiler.php | 6 +++--- .../Component/Routing/Tests/CompiledRouteTest.php | 10 +++++----- .../Component/Routing/Tests/RouteCompilerTest.php | 2 -- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index b6a7995566..62d21c37bd 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -33,22 +33,22 @@ class CompiledRoute * @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 (variables defined in the path and in the hostname patterns) * @param array $pathVariables An array of path variables - * @param array $hostnameVariables An array of hostname variables * @param array $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, array $pathVariables = array(), array $hostnameVariables = array(), $hostnameRegex = null, array $hostnameTokens = array()) + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostnameRegex = null, array $hostnameTokens = array(), array $hostnameVariables = array(), array $variables = array()) { $this->staticPrefix = $staticPrefix; $this->regex = $regex; $this->tokens = $tokens; - $this->variables = $variables; $this->pathVariables = $pathVariables; - $this->hostnameVariables = $hostnameVariables; $this->hostnameRegex = $hostnameRegex; $this->hostnameTokens = $hostnameTokens; + $this->hostnameVariables = $hostnameVariables; + $this->variables = $variables; } /** diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 418303d138..b57e0ed657 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -73,11 +73,11 @@ class RouteCompiler implements RouteCompilerInterface $staticPrefix, $regex, $tokens, - array_unique($variables), $pathVariables, - $hostnameVariables, $hostnameRegex, - $hostnameTokens + $hostnameTokens, + $hostnameVariables, + array_unique($variables) ); } 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/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index d8827df58b..cb89265b68 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -194,8 +194,6 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase $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)'); From 3dfca47bcd46c339b2a96e50393dbe5916b614a9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:45:47 +0200 Subject: [PATCH 14/28] [Routing] added some unit tests for the YAML loader --- .../Routing/Loader/AnnotationClassLoader.php | 12 ++++++------ .../Routing/Tests/Fixtures/validpattern.yml | 6 ++++-- .../Routing/Tests/Fixtures/validresource.yml | 11 ++++++----- .../Routing/Tests/Loader/YamlFileLoaderTest.php | 7 +++++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 76181df9e9..e3fe9d13ba 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,11 +97,11 @@ abstract class AnnotationClassLoader implements LoaderInterface } $globals = array( - 'pattern' => '', - 'requirements' => array(), - 'options' => array(), - 'defaults' => array(), - 'hostname_pattern' => null, + 'pattern' => '', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'hostname_pattern' => null, ); $class = new \ReflectionClass($class); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml index d5136cbab8..7f1b727c25 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: { 'foo': '\d+' } options: compiler_class: RouteCompiler diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml index e66f37613b..a5f867fa2b 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': 'foo' } + requirements: { 'foo': '\d+' } + options: { 'foo': 'bar' } + hostname_pattern: "{locale}.example.com" diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 0c870e667a..2779fb40da 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -88,6 +88,10 @@ 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('\d+', $route->getRequirement('foo')); + $this->assertEquals('{locale}.example.com', $route->getHostnamePattern()); $this->assertEquals('RouteCompiler', $route->getOption('compiler_class')); } @@ -99,9 +103,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}/blog/{slug}', $routes['blog_show']->getPattern()); + $this->assertEquals('MyBlogBundle:Blog:show', $routes['blog_show']->getDefault('_controller')); $this->assertEquals('foo', $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()); } /** From 68da6ad2921e77237b15d1f9d04ffbd2cabb3c29 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:52:06 +0200 Subject: [PATCH 15/28] [Routing] added support for hostname in the XML loader --- src/Symfony/Component/Routing/Loader/XmlFileLoader.php | 5 +++-- .../Routing/Loader/schema/routing/routing-1.0.xsd | 2 ++ .../Component/Routing/Tests/Fixtures/validpattern.xml | 2 +- .../Component/Routing/Tests/Fixtures/validresource.xml | 2 +- .../Component/Routing/Tests/Loader/XmlFileLoaderTest.php | 7 +++++++ 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 695494bd6e..81d1f704d8 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -76,6 +76,7 @@ class XmlFileLoader extends FileLoader $resource = (string) $node->getAttribute('resource'); $type = (string) $node->getAttribute('type'); $prefix = (string) $node->getAttribute('prefix'); + $hostnamePattern = (string) $node->getAttribute('hostname-pattern'); $defaults = array(); $requirements = array(); @@ -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)); @@ -154,7 +155,7 @@ class XmlFileLoader extends FileLoader } } - $route = new Route((string) $definition->getAttribute('pattern'), $defaults, $requirements, $options); + $route = new Route((string) $definition->getAttribute('pattern'), $defaults, $requirements, $options, (string) $definition->getAttribute('hostname-pattern')); $collection->add((string) $definition->getAttribute('id'), $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/Tests/Fixtures/validpattern.xml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml index fdb38073a0..f75fb1a2a7 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml @@ -4,7 +4,7 @@ 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 diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml index 14ac0fc331..9e1100684b 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml @@ -4,7 +4,7 @@ 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 \d+ diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php index e6f217606d..bf5116a859 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -48,6 +48,10 @@ 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('{locale}.example.com', $route->getHostnamePattern()); $this->assertEquals('RouteCompiler', $route->getOption('compiler_class')); } @@ -59,9 +63,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}/blog/{slug}', $routes['blog_show']->getPattern()); + $this->assertEquals('MyBundle:Blog:show', $routes['blog_show']->getDefault('_controller')); $this->assertEquals('foo', $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()); } /** From 153fcf2596725139a3204e9b10b0cd5d8b3c0fcd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 03:53:46 +0200 Subject: [PATCH 16/28] [Routing] added some unit tests for the PHP loader --- .../Component/Routing/Tests/Fixtures/validpattern.php | 5 +++-- .../Component/Routing/Tests/Loader/PhpFileLoaderTest.php | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php index 0d1d3a73da..85c18e7edf 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'), + array('compiler_class' => 'RouteCompiler'), + '{locale}.example.com' )); return $collection; 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')); } } From a270458d40c779033c469c0f592527d766daf248 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 04:01:35 +0200 Subject: [PATCH 17/28] [Routing] added some more unit tests --- .../Routing/Tests/RouteCollectionTest.php | 14 ++++++++++++++ src/Symfony/Component/Routing/Tests/RouteTest.php | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php index 14584e9cc1..00002e8c13 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.net', $routeb->getHostnamePattern()); + } } 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}'); From 148902155295aa65f1607d2bc9d7e308722c2e81 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 04:24:43 +0200 Subject: [PATCH 18/28] fixed CS --- .../Matcher/Dumper/DumperCollection.php | 22 +++++++++---------- .../Matcher/Dumper/PhpMatcherDumper.php | 9 +++----- .../Component/Routing/RouteCompiler.php | 1 - .../Dumper/PhpGeneratorDumperTest.php | 1 - 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php index cab4ad27c0..67d1170109 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php @@ -45,7 +45,7 @@ class DumperCollection implements \IteratorAggregate } /** - * Sets children + * Sets children. * * @param array $children The children */ @@ -100,9 +100,10 @@ class DumperCollection implements \IteratorAggregate } /** - * Returns true if the attribute is defined + * 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) @@ -111,23 +112,20 @@ class DumperCollection implements \IteratorAggregate } /** - * Returns an attribute by name + * Returns an attribute by name. + * + * @param string $name The attribute name + * @param mixed $default Default value is the attribute doesn't exist * - * @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) { - if ($this->hasAttribute($name)) { - return $this->attributes[$name]; - } else { - return $default; - } + return $this->hasAttribute($name) ? $this->attributes[$name] : $default; } /** - * Sets an attribute by name + * Sets an attribute by name. * * @param string $name The attribute name * @param mixed $value The attribute value @@ -138,7 +136,7 @@ class DumperCollection implements \IteratorAggregate } /** - * Sets multiple attributes + * Sets multiple attributes. * * @param array $attributes The attributes */ diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index f245818998..942809f2e5 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -117,7 +117,6 @@ EOF; foreach ($groups as $collection) { if (null !== $regex = $collection->getAttribute('hostname_regex')) { - if (!$fetchedHostname) { $code .= " \$hostname = \$this->context->getHost();\n\n"; $fetchedHostname = true; @@ -296,7 +295,6 @@ EOF; // optimize parameters array if (($matches || $hostnameMatches) && $route->getDefaults()) { - $vars = array(); if ($matches) { $vars[] = '$matches'; @@ -336,7 +334,7 @@ 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 @@ -361,10 +359,9 @@ EOF; } /** - * Groups consecutive routes having the same hostname regex + * Groups consecutive routes having the same hostname regex. * - * The results is a collection of collections of routes having the same - * hostnameRegex + * The results is a collection of collections of routes having the same hostname regex. * * @param DumperCollection $routes Flat collection of DumperRoutes * diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index b57e0ed657..b6805276ac 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -47,7 +47,6 @@ class RouteCompiler implements RouteCompilerInterface $hostnameTokens = array(); if (null !== $hostnamePattern = $route->getHostnamePattern()) { - $result = $this->compilePattern($route, $hostnamePattern, true); $hostnameVariables = $result['variables']; diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php index dee68b1957..6a86736abb 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -15,7 +15,6 @@ use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; use Symfony\Component\Routing\RequestContext; -use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; class PhpGeneratorDumperTest extends \PHPUnit_Framework_TestCase { From 562174a822ce8b1176167e2c9af40e2b8b5aac5c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Apr 2012 04:54:52 +0200 Subject: [PATCH 19/28] [Routing] fixed indentation of dumped collections --- .../Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php index aec0a9148f..7d090ff9c9 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -41,8 +41,6 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase $basePath = __DIR__.'/../../Fixtures/dumper/'; $dumper = new PhpMatcherDumper($collection); - - file_put_contents('/tmp/' . $fixture, $dumper->dump($options)); $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); } From a8ce6210b37d4d1d41c1c6fa26bd627b1a11b597 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sun, 29 Apr 2012 12:56:44 +0200 Subject: [PATCH 20/28] [Routing] added support for hostname in the apache matcher dumper --- .../Matcher/Dumper/ApacheMatcherDumper.php | 77 +++++++++++++-- .../Tests/Fixtures/dumper/url_matcher1.apache | 94 ++++++++++++++++++- .../Tests/Matcher/ApacheUrlMatcherTest.php | 14 +++ .../Dumper/ApacheMatcherDumperTest.php | 65 +++++++++++++ 4 files changed, 240 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php index e9fc82d458..f55e76cec1 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,16 @@ 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 +126,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 +148,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 +162,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/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/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; } From 8366b8ab2789edce0d21df035c02a258cebdce7d Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 5 Nov 2012 14:26:00 +0100 Subject: [PATCH 21/28] [Routing] fixed validity check for hostname params in UrlGenerator --- .../Routing/Generator/UrlGenerator.php | 25 ++++++++----- .../Tests/Generator/UrlGeneratorTest.php | 35 +++++++++++++++++++ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index 5cf7dbdcc0..535e363e88 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -199,24 +199,31 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt } if ($hostnameTokens) { - $ghost = ''; + $routeHost = ''; foreach ($hostnameTokens as $token) { if ('variable' === $token[0]) { - if (in_array($mergedParams[$token[3]], array(null, '', false), true)) { - // check requirement - if ($mergedParams[$token[3]] && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { - throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $mergedParams[$token[3]])); + 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; } - $ghost = $token[1].$mergedParams[$token[3]].$ghost; + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; } elseif ('text' === $token[0]) { - $ghost = $token[1].$ghost; + $routeHost = $token[1].$routeHost; } } - if ($ghost != $host) { - $host = $ghost; + if ($routeHost != $host) { + $host = $routeHost; $absolute = true; } } diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 080125ce11..8cadee9e65 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -398,6 +398,41 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase $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'); From 6cd34570d7f0060c0259da80ee180c4643e503f2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 5 Nov 2012 20:53:57 +0100 Subject: [PATCH 22/28] fixed CS --- .../Command/RouterDebugCommand.php | 2 +- .../Component/Routing/CompiledRoute.php | 18 ++--- .../Routing/Loader/AnnotationClassLoader.php | 2 +- .../Routing/Loader/YamlFileLoader.php | 2 +- .../Matcher/Dumper/ApacheMatcherDumper.php | 1 + .../Matcher/Dumper/DumperCollection.php | 6 +- .../Matcher/Dumper/PhpMatcherDumper.php | 18 +++-- .../Component/Routing/Matcher/UrlMatcher.php | 2 +- src/Symfony/Component/Routing/Route.php | 14 +++- .../Component/Routing/RouteCollection.php | 66 +++++++++++-------- .../Component/Routing/RouteCompiler.php | 2 +- .../Tests/Fixtures/dumper/url_matcher1.php | 4 +- .../Tests/Fixtures/dumper/url_matcher2.php | 4 +- .../Routing/Tests/Fixtures/validpattern.yml | 2 +- .../Tests/Generator/UrlGeneratorTest.php | 4 +- .../Tests/Loader/YamlFileLoaderTest.php | 2 +- .../Matcher/Dumper/DumperCollectionTest.php | 1 - .../Dumper/DumperPrefixCollectionTest.php | 1 - .../Matcher/Dumper/PhpMatcherDumperTest.php | 11 ++-- .../Routing/Tests/Matcher/UrlMatcherTest.php | 2 +- .../Routing/Tests/RouteCollectionTest.php | 2 +- .../Routing/Tests/RouteCompilerTest.php | 5 +- 22 files changed, 94 insertions(+), 77 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index b225857553..df266c8e55 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -93,7 +93,7 @@ EOF ? implode(', ', $requirements['_method']) : $requirements['_method'] ) : 'ANY'; - $hostname = null !== $route->getHostnamePattern() + $hostname = '' !== $route->getHostnamePattern() ? $route->getHostnamePattern() : 'ANY'; $maxName = max($maxName, strlen($name)); diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index 62d21c37bd..7f02f60443 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -30,14 +30,14 @@ class CompiledRoute /** * 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 $pathVariables An array of path variables - * @param array $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) + * @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 array $hostnameRegex|null 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 $pathVariables, $hostnameRegex = null, array $hostnameTokens = array(), array $hostnameVariables = array(), array $variables = array()) { @@ -74,7 +74,7 @@ class CompiledRoute /** * Returns the hostname regex * - * @return string The hostname regex + * @return string|null The hostname regex or null */ public function getHostnameRegex() { diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index e3fe9d13ba..6abca6ac71 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -101,7 +101,7 @@ abstract class AnnotationClassLoader implements LoaderInterface 'requirements' => array(), 'options' => array(), 'defaults' => array(), - 'hostname_pattern' => null, + 'hostname_pattern' => '', ); $class = new \ReflectionClass($class); diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index e75dc202b6..cf981d2b57 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -70,7 +70,7 @@ 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; + $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, $hostnamePattern); diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php index f55e76cec1..73ace8afa6 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php @@ -111,6 +111,7 @@ class ApacheMatcherDumper extends MatcherDumper * @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) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php index 67d1170109..bba0c27d0b 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php @@ -102,7 +102,7 @@ class DumperCollection implements \IteratorAggregate /** * Returns true if the attribute is defined. * - * @param string $name The attribute name + * @param string $name The attribute name * * @return Boolean true if the attribute is defined, false otherwise */ @@ -117,7 +117,7 @@ class DumperCollection implements \IteratorAggregate * @param string $name The attribute name * @param mixed $default Default value is the attribute doesn't exist * - * @return mixed The attribute value + * @return mixed The attribute value */ public function getAttribute($name, $default = null) { @@ -138,7 +138,7 @@ class DumperCollection implements \IteratorAggregate /** * Sets multiple attributes. * - * @param array $attributes The attributes + * @param array $attributes The attributes */ public function setAttributes($attributes) { diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index 942809f2e5..18c1f9ce96 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -144,9 +144,9 @@ EOF; /** * 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 */ @@ -302,7 +302,11 @@ EOF; if ($hostnameMatches) { $vars[] = '$hostnameMatches'; } - $matchesExpr = implode(' + ', $vars); + if (count($vars) > 1) { + $matchesExpr = 'array_merge(' . implode(', ', array_reverse($vars)) . ')'; + } else { + $matchesExpr = current($vars); + } $code .= sprintf(" return array_merge(\$this->mergeDefaults(%s, %s), array('_route' => '%s'));\n" , $matchesExpr, str_replace("\n", '', var_export($route->getDefaults(), true)), $name); @@ -313,7 +317,7 @@ EOF; $code .= " \$matches = \$hostnameMatches;\n"; } else { if ($hostnameMatches) { - $code .= " \$matches = \$matches + \$hostnameMatches;\n"; + $code .= " \$matches = array_merge(\$hostnameMatches, \$matches);\n"; } } @@ -336,8 +340,8 @@ EOF; /** * 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 */ @@ -394,7 +398,7 @@ EOF; * 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 */ diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 3c0db6dd7b..26812edb43 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -147,7 +147,7 @@ class UrlMatcher implements UrlMatcherInterface continue; } - return array_merge($this->mergeDefaults($hostnameMatches + $matches, $route->getDefaults()), array('_route' => $name)); + return array_merge($this->mergeDefaults(array_merge($matches, $hostnameMatches), $route->getDefaults()), array('_route' => $name)); } } diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php index bbd882f09d..a13c3c77b7 100644 --- a/src/Symfony/Component/Routing/Route.php +++ b/src/Symfony/Component/Routing/Route.php @@ -44,7 +44,7 @@ class Route implements \Serializable * * @api */ - public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array(), $hostnamePattern = null) + public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array(), $hostnamePattern = '') { $this->setPattern($pattern); $this->setDefaults($defaults); @@ -106,14 +106,24 @@ 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 = $pattern; + $this->hostnamePattern = (string) $pattern; return $this; } diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index ae2755205f..d871442ac5 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -42,7 +42,7 @@ class RouteCollection implements \IteratorAggregate, \Countable $this->routes = array(); $this->resources = array(); $this->prefix = ''; - $this->hostnamePattern = null; + $this->hostnamePattern = ''; } public function __clone() @@ -185,18 +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 string $hostnamePattern Hostname pattern + * @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(), $hostnamePattern = null) + public function addCollection(RouteCollection $collection, $prefix = '', $defaults = array(), $requirements = array(), $options = array(), $hostnamePattern = '') { // prevent infinite loops by recursive referencing $root = $this->getRoot(); @@ -212,10 +212,7 @@ class RouteCollection implements \IteratorAggregate, \Countable // necessarily already have it applied (depending on the order RouteCollections are added to each other) $collection->addPrefix($this->getPrefix() . $prefix, $defaults, $requirements, $options); - // Allow child collection to have a different pattern - if (!$collection->getHostnamePattern()) { - $collection->setHostnamePattern($hostnamePattern); - } + $collection->setHostnamePattern($hostnamePattern); $this->routes[] = $collection; } @@ -268,6 +265,34 @@ 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 = $pattern; + + if ('' === $pattern) { + return; + } + + foreach ($this->routes as $name => $route) { + $route->setHostnamePattern($pattern); + } + } + /** * Returns an array of resources loaded to build this collection. * @@ -350,21 +375,4 @@ class RouteCollection implements \IteratorAggregate, \Countable return false; } - - public function getHostnamePattern() - { - return $this->hostnamePattern; - } - - public function setHostnamePattern($pattern) - { - $this->hostnamePattern = $pattern; - - foreach ($this->routes as $name => $route) { - // Allow individual routes to have a different pattern - if (!$route->getHostnamePattern()) { - $route->setHostnamePattern($pattern); - } - } - } } diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index b6805276ac..e53e23e78e 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -46,7 +46,7 @@ class RouteCompiler implements RouteCompilerInterface $hostnameRegex = null; $hostnameTokens = array(); - if (null !== $hostnamePattern = $route->getHostnamePattern()) { + if ('' !== $hostnamePattern = $route->getHostnamePattern()) { $result = $this->compilePattern($route, $hostnamePattern, true); $hostnameVariables = $result['variables']; 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 8111e41715..dec8ba4a40 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -282,7 +282,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher // route13 if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches = $matches + $hostnameMatches; + $matches = array_merge($hostnameMatches, $matches); $matches['_route'] = 'route13'; return $matches; @@ -290,7 +290,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher // route14 if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) { - return array_merge($this->mergeDefaults($matches + $hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route14')); + return array_merge($this->mergeDefaults(array_merge($hostnameMatches, $matches), array ( 'var1' => 'val',)), array('_route' => 'route14')); } } 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 16040bc29c..607b53fb1d 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -294,7 +294,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec // route13 if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches = $matches + $hostnameMatches; + $matches = array_merge($hostnameMatches, $matches); $matches['_route'] = 'route13'; return $matches; @@ -302,7 +302,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec // route14 if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) { - return array_merge($this->mergeDefaults($matches + $hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route14')); + return array_merge($this->mergeDefaults(array_merge($hostnameMatches, $matches), array ( 'var1' => 'val',)), array('_route' => 'route14')); } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml index 7f1b727c25..b85bab3465 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml @@ -2,6 +2,6 @@ blog_show: pattern: /blog/{slug} defaults: { _controller: MyBlogBundle:Blog:show } hostname_pattern: "{locale}.example.com" - requirements: { 'foo': '\d+' } + requirements: { 'locale': '\d+' } options: compiler_class: RouteCompiler diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 8cadee9e65..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')); diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 2779fb40da..b2c2d6b969 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -90,7 +90,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase $route = $routes['blog_show']; $this->assertEquals('/blog/{slug}', $route->getPattern()); $this->assertEquals('MyBlogBundle:Blog:show', $route->getDefault('_controller')); - $this->assertEquals('\d+', $route->getRequirement('foo')); + $this->assertEquals('\d+', $route->getRequirement('locale')); $this->assertEquals('{locale}.example.com', $route->getHostnamePattern()); $this->assertEquals('RouteCompiler', $route->getOption('compiler_class')); } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php index 7f2d914424..62ca947848 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php @@ -22,4 +22,3 @@ class DumperCollectionTest extends \PHPUnit_Framework_TestCase $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 7d090ff9c9..9c15be5642 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -14,7 +14,6 @@ namespace Symfony\Component\Routing\Tests\Matcher\Dumper; use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase { @@ -165,13 +164,11 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase $collection2 = new RouteCollection(); - $route2 = new Route('/route2', array(), array(), array(), 'a.example.com'); - $collection2->add('route2', $route2); + $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com'); + $collection1->add('route2', $route2); - $route3 = new Route('/route3', array(), array(), array(), 'b.example.com'); - $collection2->add('route3', $route3); - - $collection1->addCollection($collection2, '/c2'); + $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); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 6e1f804810..8105528885 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -348,7 +348,7 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase $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.net')); + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar')); } diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php index 00002e8c13..16d193e34b 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php @@ -309,6 +309,6 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase $collection->setHostnamePattern('{locale}.example.com'); $this->assertEquals('{locale}.example.com', $routea->getHostnamePattern()); - $this->assertEquals('{locale}.example.net', $routeb->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 cb89265b68..40395e175e 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -197,7 +197,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase $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() { @@ -247,8 +247,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array('text', '.example'), array('variable', '', '[^\.]++', 'locale'), ), - ), + ), ); } } - From 9ffe3de642253fcaf2d5535d5ae37ed94630e71a Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Mon, 5 Nov 2012 17:32:36 +0100 Subject: [PATCH 23/28] synchronize the fixtures in different formats and fix default for numeric requirement --- .../Component/Routing/Tests/Fixtures/validpattern.php | 2 +- .../Component/Routing/Tests/Fixtures/validpattern.xml | 1 + .../Component/Routing/Tests/Fixtures/validpattern.yml | 2 +- .../Component/Routing/Tests/Fixtures/validresource.xml | 2 +- .../Component/Routing/Tests/Fixtures/validresource.yml | 2 +- .../Component/Routing/Tests/Loader/XmlFileLoaderTest.php | 3 ++- .../Component/Routing/Tests/Loader/YamlFileLoaderTest.php | 5 +++-- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php index 85c18e7edf..08e7d6e347 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php @@ -6,7 +6,7 @@ $collection = new RouteCollection(); $collection->add('blog_show', new Route( '/blog/{slug}', array('_controller' => 'MyBlogBundle:Blog:show'), - array('_method' => 'GET'), + array('_method' => 'GET', 'locale' => '\w+'), array('compiler_class' => 'RouteCompiler'), '{locale}.example.com' )); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml index f75fb1a2a7..079116399c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml @@ -7,6 +7,7 @@ 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 b85bab3465..8fad8a0923 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml @@ -2,6 +2,6 @@ blog_show: pattern: /blog/{slug} defaults: { _controller: MyBlogBundle:Blog:show } hostname_pattern: "{locale}.example.com" - requirements: { 'locale': '\d+' } + 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 9e1100684b..b3747864fb 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.xml @@ -5,7 +5,7 @@ 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 a5f867fa2b..35a45398ff 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.yml @@ -1,7 +1,7 @@ blog_show: resource: validpattern.yml prefix: /{foo} - defaults: { 'foo': 'foo' } + defaults: { 'foo': '123' } requirements: { 'foo': '\d+' } options: { 'foo': 'bar' } hostname_pattern: "{locale}.example.com" diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php index bf5116a859..235a9d13af 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -51,6 +51,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase $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')); } @@ -65,7 +66,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); $this->assertEquals('/{foo}/blog/{slug}', $routes['blog_show']->getPattern()); $this->assertEquals('MyBundle:Blog:show', $routes['blog_show']->getDefault('_controller')); - $this->assertEquals('foo', $routes['blog_show']->getDefault('foo')); + $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 b2c2d6b969..2606062a94 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -90,7 +90,8 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase $route = $routes['blog_show']; $this->assertEquals('/blog/{slug}', $route->getPattern()); $this->assertEquals('MyBlogBundle:Blog:show', $route->getDefault('_controller')); - $this->assertEquals('\d+', $route->getRequirement('locale')); + $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')); } @@ -105,7 +106,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); $this->assertEquals('/{foo}/blog/{slug}', $routes['blog_show']->getPattern()); $this->assertEquals('MyBlogBundle:Blog:show', $routes['blog_show']->getDefault('_controller')); - $this->assertEquals('foo', $routes['blog_show']->getDefault('foo')); + $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()); From 94ec65381826faf6761731e507ca27591197864a Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Mon, 5 Nov 2012 18:21:19 +0100 Subject: [PATCH 24/28] removed irrelevant string case in XmlFileLoader getAttribute() always returns a string --- .../Routing/Loader/XmlFileLoader.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 81d1f704d8..9c196a8d07 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -73,10 +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'); - $hostnamePattern = (string) $node->getAttribute('hostname-pattern'); + $resource = $node->getAttribute('resource'); + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $hostnamePattern = $node->getAttribute('hostname-pattern'); $defaults = array(); $requirements = array(); @@ -89,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)); @@ -142,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, (string) $definition->getAttribute('hostname-pattern')); + $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); } /** From 7ed3013a5b9323f7d5d526aa5fe8bdaaeaad2950 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Sun, 11 Nov 2012 19:07:05 +0100 Subject: [PATCH 25/28] switch to array_replace instead of array_merge we don't need the logic to merge numeric keys, as we don't have them. I could also improve the genrated code by PhpMatcherDumper a little by saving a function call. --- .../Routing/Loader/AnnotationClassLoader.php | 6 ++--- .../Matcher/Dumper/PhpMatcherDumper.php | 24 +++++++------------ .../Component/Routing/Matcher/UrlMatcher.php | 4 ++-- .../Tests/Fixtures/dumper/url_matcher1.php | 12 +++++----- .../Tests/Fixtures/dumper/url_matcher2.php | 12 +++++----- 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 6abca6ac71..7fb66a6124 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -153,14 +153,14 @@ 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()); $hostnamePattern = $annot->getHostnamePattern(); if (null === $hostnamePattern) { diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index 18c1f9ce96..fb147bced5 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); @@ -296,35 +296,29 @@ EOF; // optimize parameters array if (($matches || $hostnameMatches) && $route->getDefaults()) { $vars = array(); - if ($matches) { - $vars[] = '$matches'; - } if ($hostnameMatches) { $vars[] = '$hostnameMatches'; } - if (count($vars) > 1) { - $matchesExpr = 'array_merge(' . implode(', ', array_reverse($vars)) . ')'; - } else { - $matchesExpr = current($vars); + if ($matches) { + $vars[] = '$matches'; } + $vars[] = "array('_route' => '$name')"; - $code .= sprintf(" return array_merge(\$this->mergeDefaults(%s, %s), array('_route' => '%s'));\n" - , $matchesExpr, str_replace("\n", '', var_export($route->getDefaults(), true)), $name); + $code .= sprintf(" return \$this->mergeDefaults(array_replace(%s), %s);\n" + , implode(', ', $vars), str_replace("\n", '', var_export($route->getDefaults(), true))); } elseif ($matches || $hostnameMatches) { if (!$matches) { $code .= " \$matches = \$hostnameMatches;\n"; - } else { - if ($hostnameMatches) { - $code .= " \$matches = array_merge(\$hostnameMatches, \$matches);\n"; - } + } elseif ($hostnameMatches) { + $code .= " \$matches = array_replace(\$hostnameMatches, \$matches);\n"; } $code .= sprintf(" \$matches['_route'] = '%s';\n\n", $name); $code .= " return \$matches;\n"; } 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); } diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 26812edb43..2afe0f738e 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -117,8 +117,8 @@ class UrlMatcher implements UrlMatcherInterface if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { continue; } - $hostnameMatches = array(); + $hostnameMatches = array(); if ($compiledRoute->getHostnameRegex() && !preg_match($compiledRoute->getHostnameRegex(), $this->context->getHost(), $hostnameMatches)) { continue; } @@ -147,7 +147,7 @@ class UrlMatcher implements UrlMatcherInterface continue; } - return array_merge($this->mergeDefaults(array_merge($matches, $hostnameMatches), $route->getDefaults()), array('_route' => $name)); + return $this->mergeDefaults(array_replace($matches, $hostnameMatches, array('_route' => $name)), $route->getDefaults()); } } 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 dec8ba4a40..98d3cce44e 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')) { @@ -177,7 +177,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 @@ -277,12 +277,12 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher // route12 if ($pathinfo === '/route12') { - return array_merge($this->mergeDefaults($hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route12')); + return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); } // route13 if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches = array_merge($hostnameMatches, $matches); + $matches = array_replace($hostnameMatches, $matches); $matches['_route'] = 'route13'; return $matches; @@ -290,7 +290,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher // route14 if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) { - return array_merge($this->mergeDefaults(array_merge($hostnameMatches, $matches), array ( 'var1' => 'val',)), array('_route' => 'route14')); + return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); } } @@ -310,7 +310,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (0 === strpos($pathinfo, '/route1')) { // route16 if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?[^/]++)$#s', $pathinfo, $matches)) { - return array_merge($this->mergeDefaults($matches, array ( 'var1' => 'val',)), array('_route' => 'route16')); + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); } // route17 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 607b53fb1d..f4de7eb1b2 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')) { @@ -185,7 +185,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 @@ -289,12 +289,12 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec // route12 if ($pathinfo === '/route12') { - return array_merge($this->mergeDefaults($hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route12')); + return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); } // route13 if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches = array_merge($hostnameMatches, $matches); + $matches = array_replace($hostnameMatches, $matches); $matches['_route'] = 'route13'; return $matches; @@ -302,7 +302,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec // route14 if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?[^/]++)$#s', $pathinfo, $matches)) { - return array_merge($this->mergeDefaults(array_merge($hostnameMatches, $matches), array ( 'var1' => 'val',)), array('_route' => 'route14')); + return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); } } @@ -322,7 +322,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (0 === strpos($pathinfo, '/route1')) { // route16 if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?[^/]++)$#s', $pathinfo, $matches)) { - return array_merge($this->mergeDefaults($matches, array ( 'var1' => 'val',)), array('_route' => 'route16')); + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); } // route17 From 514e27a5110dd28f1d9ab3fbfdb3ca93999c729a Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Sun, 11 Nov 2012 20:38:13 +0100 Subject: [PATCH 26/28] [Routing] fix PhpMatcherDumper that returned numeric-indexed params that are returned besides named placeholders by preg_match --- .../Matcher/Dumper/PhpMatcherDumper.php | 12 +-- .../Tests/Fixtures/dumper/url_matcher1.php | 78 +++++-------------- .../Tests/Fixtures/dumper/url_matcher2.php | 78 +++++-------------- .../Tests/Fixtures/dumper/url_matcher3.php | 4 +- 4 files changed, 40 insertions(+), 132 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index fb147bced5..12aa644694 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -294,7 +294,7 @@ EOF; } // optimize parameters array - if (($matches || $hostnameMatches) && $route->getDefaults()) { + if ($matches || $hostnameMatches) { $vars = array(); if ($hostnameMatches) { $vars[] = '$hostnameMatches'; @@ -307,16 +307,6 @@ EOF; $code .= sprintf(" return \$this->mergeDefaults(array_replace(%s), %s);\n" , implode(', ', $vars), str_replace("\n", '', var_export($route->getDefaults(), true))); - } elseif ($matches || $hostnameMatches) { - - if (!$matches) { - $code .= " \$matches = \$hostnameMatches;\n"; - } elseif ($hostnameMatches) { - $code .= " \$matches = array_replace(\$hostnameMatches, \$matches);\n"; - } - - $code .= sprintf(" \$matches['_route'] = '%s';\n\n", $name); - $code .= " return \$matches;\n"; } elseif ($route->getDefaults()) { $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); } else { 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 98d3cce44e..19ed9ca13f 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -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 ()); } } @@ -194,16 +172,12 @@ 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, '/aba')) { @@ -214,9 +188,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher // foo4 if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches['_route'] = 'foo4'; - - return $matches; + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); } } @@ -269,10 +241,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (0 === strpos($pathinfo, '/route1')) { // route11 if ($pathinfo === '/route11') { - $matches = $hostnameMatches; - $matches['_route'] = 'route11'; - - return $matches; + return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route11')), array ()); } // route12 @@ -282,10 +251,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher // route13 if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches = array_replace($hostnameMatches, $matches); - $matches['_route'] = 'route13'; - - return $matches; + return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route13')), array ()); } // route14 @@ -300,9 +266,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) { // route15 if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches['_route'] = 'route15'; - - return $matches; + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); } } @@ -329,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 f4de7eb1b2..6063042d0d 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -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 ()); } } @@ -206,16 +184,12 @@ 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, '/aba')) { @@ -226,9 +200,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec // foo4 if (preg_match('#^/aba/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches['_route'] = 'foo4'; - - return $matches; + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); } } @@ -281,10 +253,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (0 === strpos($pathinfo, '/route1')) { // route11 if ($pathinfo === '/route11') { - $matches = $hostnameMatches; - $matches['_route'] = 'route11'; - - return $matches; + return $this->mergeDefaults(array_replace($hostnameMatches, array('_route' => 'route11')), array ()); } // route12 @@ -294,10 +263,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec // route13 if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches = array_replace($hostnameMatches, $matches); - $matches['_route'] = 'route13'; - - return $matches; + return $this->mergeDefaults(array_replace($hostnameMatches, $matches, array('_route' => 'route13')), array ()); } // route14 @@ -312,9 +278,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) { // route15 if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?[^/]++)$#s', $pathinfo, $matches)) { - $matches['_route'] = 'route15'; - - return $matches; + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); } } @@ -341,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 ()); } } From 26e56842dc00e2a402b058d330aeb30ef53dd804 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Sun, 11 Nov 2012 21:05:22 +0100 Subject: [PATCH 27/28] some type fixes --- .../Command/RouterDebugCommand.php | 3 +-- .../Component/Routing/CompiledRoute.php | 18 +++++++++--------- .../Matcher/Dumper/DumperCollection.php | 1 + .../Component/Routing/RouteCollection.php | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index df266c8e55..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 @@ -112,7 +111,7 @@ EOF ? implode(', ', $requirements['_method']) : $requirements['_method'] ) : 'ANY'; - $hostname = null !== $route->getHostnamePattern() + $hostname = '' !== $route->getHostnamePattern() ? $route->getHostnamePattern() : 'ANY'; $output->writeln(sprintf($format, $name, $method, $hostname, $route->getPattern())); } diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index 7f02f60443..1ccad3c22a 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -30,18 +30,18 @@ class CompiledRoute /** * 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 $pathVariables An array of path variables - * @param array $hostnameRegex|null 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) + * @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 $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; diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php index bba0c27d0b..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. diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index d871442ac5..38c82a85f6 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -282,7 +282,7 @@ class RouteCollection implements \IteratorAggregate, \Countable */ public function setHostnamePattern($pattern) { - $this->hostnamePattern = $pattern; + $this->hostnamePattern = (string) $pattern; if ('' === $pattern) { return; From e120a7acba03176ae0340e457d594f59b835c0ce Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Mon, 12 Nov 2012 13:06:49 +0100 Subject: [PATCH 28/28] fix API of RouteCollection --- src/Symfony/Component/Routing/RouteCollection.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 38c82a85f6..7fc0304211 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -212,7 +212,9 @@ class RouteCollection implements \IteratorAggregate, \Countable // necessarily already have it applied (depending on the order RouteCollections are added to each other) $collection->addPrefix($this->getPrefix() . $prefix, $defaults, $requirements, $options); - $collection->setHostnamePattern($hostnamePattern); + if ('' !== $hostnamePattern) { + $collection->setHostnamePattern($hostnamePattern); + } $this->routes[] = $collection; } @@ -284,10 +286,6 @@ class RouteCollection implements \IteratorAggregate, \Countable { $this->hostnamePattern = (string) $pattern; - if ('' === $pattern) { - return; - } - foreach ($this->routes as $name => $route) { $route->setHostnamePattern($pattern); }