diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index 3eca9ecd8b010..3f67233e97bda 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -27,19 +27,31 @@ class CompiledRoute implements \Serializable private $hostRegex; private $hostTokens; + /** + * @var array + */ + private $regexVariablesAliases; + + /** + * @var array + */ + private $hostRegexVariablesAliases; + /** * 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 string|null $hostRegex Host regex - * @param array $hostTokens Host tokens - * @param array $hostVariables An array of host variables - * @param array $variables An array of variables (variables defined in the path and in the host 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 $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + * @param array $regexVariablesAliases An array containing path variables aliases as keys and actual path variables names as values + * @param array $hostRegexVariablesAliases An array containing host variables aliases as keys and actual host variables names as values */ - public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array(), array $regexVariablesAliases = array(), array $hostRegexVariablesAliases = array()) { $this->staticPrefix = (string) $staticPrefix; $this->regex = $regex; @@ -49,6 +61,8 @@ public function __construct($staticPrefix, $regex, array $tokens, array $pathVar $this->hostTokens = $hostTokens; $this->hostVariables = $hostVariables; $this->variables = $variables; + $this->regexVariablesAliases = $regexVariablesAliases; + $this->hostRegexVariablesAliases = $hostRegexVariablesAliases; } /** @@ -62,9 +76,11 @@ public function serialize() 'path_regex' => $this->regex, 'path_tokens' => $this->tokens, 'path_vars' => $this->pathVariables, + 'path_regex_vars_aliases' => $this->regexVariablesAliases, 'host_regex' => $this->hostRegex, 'host_tokens' => $this->hostTokens, 'host_vars' => $this->hostVariables, + 'host_regex_vars_aliases' => $this->hostRegexVariablesAliases, )); } @@ -79,9 +95,11 @@ public function unserialize($serialized) $this->regex = $data['path_regex']; $this->tokens = $data['path_tokens']; $this->pathVariables = $data['path_vars']; + $this->regexVariablesAliases = $data['path_regex_vars_aliases']; $this->hostRegex = $data['host_regex']; $this->hostTokens = $data['host_tokens']; $this->hostVariables = $data['host_vars']; + $this->hostRegexVariablesAliases = $data['host_regex_vars_aliases']; } /** @@ -163,4 +181,20 @@ public function getHostVariables() { return $this->hostVariables; } + + /** + * @return array + */ + public function getRegexVariablesAliases() + { + return $this->regexVariablesAliases; + } + + /** + * @return array + */ + public function getHostRegexVariablesAliases() + { + return $this->hostRegexVariablesAliases; + } } diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index aa7df8a78b5ba..00e04f2fd613f 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -316,9 +316,25 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren $vars = array(); if ($hostMatches) { $vars[] = '$hostMatches'; + foreach ($compiledRoute->getHostRegexVariablesAliases() as $hostRegexVariableAlias => $actualHostRegexVariableName) { + $code .= <<getRegexVariablesAliases() as $regexVariableAlias => $actualRegexVariableName) { + $code .= <<getHost()) { @@ -50,6 +51,7 @@ public static function compile(Route $route) $hostTokens = $result['tokens']; $hostRegex = $result['regex']; + $hostRegexVariablesAliases = $result['regex_variables_aliases']; } $path = $route->getPath(); @@ -63,6 +65,7 @@ public static function compile(Route $route) $tokens = $result['tokens']; $regex = $result['regex']; + $regexVariablesAliases = $result['regex_variables_aliases']; return new CompiledRoute( $staticPrefix, @@ -72,7 +75,9 @@ public static function compile(Route $route) $hostRegex, $hostTokens, $hostVariables, - array_unique($variables) + array_unique($variables), + $regexVariablesAliases, + $hostRegexVariablesAliases ); } @@ -142,10 +147,12 @@ private static function compilePattern(Route $route, $pattern, $isHost) $tokens[] = array('text', substr($pattern, $pos)); } + $tokensCount = count($tokens); + // find the first optional token $firstOptional = PHP_INT_MAX; if (!$isHost) { - for ($i = count($tokens) - 1; $i >= 0; --$i) { + for ($i = $tokensCount - 1; $i >= 0; --$i) { $token = $tokens[$i]; if ('variable' === $token[0] && $route->hasDefault($token[3])) { $firstOptional = $i; @@ -157,8 +164,26 @@ private static function compilePattern(Route $route, $pattern, $isHost) // compute the matching regexp $regexp = ''; - for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { - $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + $regexpVariablesAliases = array(); + for ($i = 0, $nbToken = $tokensCount; $i < $nbToken; ++$i) { + $token = $tokens[$i]; + switch ($token[0]) { + case 'text': + $regexp .= self::computeRegexpForTextToken($token); + break; + case 'variable': + list($tokenRegexp, $regexpVariableName) = self::computeRegexpForVariableToken($token, $i, $tokensCount, $firstOptional, $variables); + $regexp .= $tokenRegexp; + + $variableName = $token[3]; + if ($regexpVariableName !== $variableName) { + $regexpVariablesAliases[$regexpVariableName] = $variableName; + } + + break; + default: + throw new \LogicException('The token type should be "text" or "variable".'); + } } return array( @@ -166,6 +191,7 @@ private static function compilePattern(Route $route, $pattern, $isHost) 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''), 'tokens' => array_reverse($tokens), 'variables' => $variables, + 'regex_variables_aliases' => $regexpVariablesAliases, ); } @@ -189,41 +215,59 @@ private static function findNextSeparator($pattern) } /** - * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * Computes the regexp used to match a static text token. + * + * @param array $token The static text token * - * @param array $tokens The route tokens - * @param int $index The index of the current token + * @return string The regexp pattern of the token + */ + private static function computeRegexpForTextToken(array $token) + { + return preg_quote($token[1], self::REGEX_DELIMITER); + } + + /** + * Computes the regexp used to match a subpattern token. + * + * @param array $token The subpattern token + * @param int $index The index of the token + * @param int $tokensCount The total number of tokens of the route * @param int $firstOptional The index of the first optional token + * @param array $variables All the variables names of the route * - * @return string The regexp pattern for a single token + * @return array An array containing the regexp pattern of the token, and the variable name that is used in this regexp pattern */ - private static function computeRegexp(array $tokens, $index, $firstOptional) + private static function computeRegexpForVariableToken(array $token, $index, $tokensCount, $firstOptional, array $variables) { - $token = $tokens[$index]; - if ('text' === $token[0]) { - // Text tokens - return preg_quote($token[1], self::REGEX_DELIMITER); + $variableName = $token[3]; + // 32 is the maximum length for a PCRE subpattern name => http://pcre.org/current/doc/html/pcre2pattern.html#SEC16 + if (strlen($variableName) > 32) { + $i = 0; + do { + $variableName = sprintf('variableAlias%s', ++$i); + } while (in_array($variableName, $variables)); + } + + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + $regexp = sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $variableName, $token[2]); } else { - // Variable tokens - if (0 === $index && 0 === $firstOptional) { - // When the only token is an optional variable token, the separator is required - return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); - } else { - $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); - if ($index >= $firstOptional) { - // Enclose each optional token in a subpattern to make it optional. - // "?:" means it is non-capturing, i.e. the portion of the subject string that - // matched the optional subpattern is not passed back. - $regexp = "(?:$regexp"; - $nbTokens = count($tokens); - if ($nbTokens - 1 == $index) { - // Close the optional subpatterns - $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); - } + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $variableName, $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + if ($tokensCount - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $tokensCount - $firstOptional - (0 === $firstOptional ? 1 : 0)); } - - return $regexp; } } + + return array( + $regexp, + $variableName, + ); } } diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index b4b4f45a8379f..69dca609fd0a8 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -162,6 +162,15 @@ public function provideCompileData() array('text', '/foo'), ), ), + + array( + 'Route with a variable name longer than 32 characters', + array('/foo/{pneumonoultramicroscopicsilicovolcanoconiosis}'), + '/foo', '#^/foo/(?P[^/]++)$#s', array('pneumonoultramicroscopicsilicovolcanoconiosis'), array( + array('variable', '/', '[^/]++', 'pneumonoultramicroscopicsilicovolcanoconiosis'), + array('text', '/foo'), + ), + ), ); } @@ -262,6 +271,17 @@ public function provideCompileWithHostData() array('variable', '', '[^\.]++', 'locale'), ), ), + array( + 'Route with a variable name longer than 32 characters in the host', + array('/hello', array(), array(), array(), 'www.example.{pneumonoultramicroscopicsilicovolcanoconiosis}'), + '/hello', '#^/hello$#s', array('pneumonoultramicroscopicsilicovolcanoconiosis'), array(), array( + array('text', '/hello'), + ), + '#^www\.example\.(?P[^\.]++)$#si', array('pneumonoultramicroscopicsilicovolcanoconiosis'), array( + array('variable', '.', '[^\.]++', 'pneumonoultramicroscopicsilicovolcanoconiosis'), + array('text', 'www.example'), + ), + ), ); } }