[ Index ] |
PHP Cross Reference of phpBB-3.1.12-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12 namespace Symfony\Component\Routing; 13 14 /** 15 * RouteCompiler compiles Route instances to CompiledRoute instances. 16 * 17 * @author Fabien Potencier <fabien@symfony.com> 18 * @author Tobias Schultze <http://tobion.de> 19 */ 20 class RouteCompiler implements RouteCompilerInterface 21 { 22 const REGEX_DELIMITER = '#'; 23 24 /** 25 * This string defines the characters that are automatically considered separators in front of 26 * optional placeholders (with default and no static text following). Such a single separator 27 * can be left out together with the optional placeholder from matching and generating URLs. 28 */ 29 const SEPARATORS = '/,;.:-_~+*=@|'; 30 31 /** 32 * {@inheritdoc} 33 * 34 * @throws \LogicException If a variable is referenced more than once 35 * @throws \DomainException If a variable name is numeric because PHP raises an error for such 36 * subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)". 37 */ 38 public static function compile(Route $route) 39 { 40 $hostVariables = array(); 41 $variables = array(); 42 $hostRegex = null; 43 $hostTokens = array(); 44 45 if ('' !== $host = $route->getHost()) { 46 $result = self::compilePattern($route, $host, true); 47 48 $hostVariables = $result['variables']; 49 $variables = $hostVariables; 50 51 $hostTokens = $result['tokens']; 52 $hostRegex = $result['regex']; 53 } 54 55 $path = $route->getPath(); 56 57 $result = self::compilePattern($route, $path, false); 58 59 $staticPrefix = $result['staticPrefix']; 60 61 $pathVariables = $result['variables']; 62 $variables = array_merge($variables, $pathVariables); 63 64 $tokens = $result['tokens']; 65 $regex = $result['regex']; 66 67 return new CompiledRoute( 68 $staticPrefix, 69 $regex, 70 $tokens, 71 $pathVariables, 72 $hostRegex, 73 $hostTokens, 74 $hostVariables, 75 array_unique($variables) 76 ); 77 } 78 79 private static function compilePattern(Route $route, $pattern, $isHost) 80 { 81 $tokens = array(); 82 $variables = array(); 83 $matches = array(); 84 $pos = 0; 85 $defaultSeparator = $isHost ? '.' : '/'; 86 87 // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable 88 // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. 89 preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); 90 foreach ($matches as $match) { 91 $varName = substr($match[0][0], 1, -1); 92 // get all static text preceding the current variable 93 $precedingText = substr($pattern, $pos, $match[0][1] - $pos); 94 $pos = $match[0][1] + strlen($match[0][0]); 95 $precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : ''; 96 $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); 97 98 if (is_numeric($varName)) { 99 throw new \DomainException(sprintf('Variable name "%s" cannot be numeric in route pattern "%s". Please use a different name.', $varName, $pattern)); 100 } 101 if (in_array($varName, $variables)) { 102 throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); 103 } 104 105 if ($isSeparator && strlen($precedingText) > 1) { 106 $tokens[] = array('text', substr($precedingText, 0, -1)); 107 } elseif (!$isSeparator && strlen($precedingText) > 0) { 108 $tokens[] = array('text', $precedingText); 109 } 110 111 $regexp = $route->getRequirement($varName); 112 if (null === $regexp) { 113 $followingPattern = (string) substr($pattern, $pos); 114 // Find the next static character after the variable that functions as a separator. By default, this separator and '/' 115 // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all 116 // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are 117 // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) 118 // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. 119 // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally 120 // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. 121 $nextSeparator = self::findNextSeparator($followingPattern); 122 $regexp = sprintf( 123 '[^%s%s]+', 124 preg_quote($defaultSeparator, self::REGEX_DELIMITER), 125 $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' 126 ); 127 if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { 128 // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive 129 // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. 130 // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow 131 // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is 132 // directly adjacent, e.g. '/{x}{y}'. 133 $regexp .= '+'; 134 } 135 } 136 137 $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); 138 $variables[] = $varName; 139 } 140 141 if ($pos < strlen($pattern)) { 142 $tokens[] = array('text', substr($pattern, $pos)); 143 } 144 145 // find the first optional token 146 $firstOptional = PHP_INT_MAX; 147 if (!$isHost) { 148 for ($i = count($tokens) - 1; $i >= 0; --$i) { 149 $token = $tokens[$i]; 150 if ('variable' === $token[0] && $route->hasDefault($token[3])) { 151 $firstOptional = $i; 152 } else { 153 break; 154 } 155 } 156 } 157 158 // compute the matching regexp 159 $regexp = ''; 160 for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { 161 $regexp .= self::computeRegexp($tokens, $i, $firstOptional); 162 } 163 164 return array( 165 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', 166 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''), 167 'tokens' => array_reverse($tokens), 168 'variables' => $variables, 169 ); 170 } 171 172 /** 173 * Returns the next static character in the Route pattern that will serve as a separator. 174 * 175 * @param string $pattern The route pattern 176 * 177 * @return string The next static character that functions as separator (or empty string when none available) 178 */ 179 private static function findNextSeparator($pattern) 180 { 181 if ('' == $pattern) { 182 // return empty string if pattern is empty or false (false which can be returned by substr) 183 return ''; 184 } 185 // first remove all placeholders from the pattern so we can find the next real static character 186 $pattern = preg_replace('#\{\w+\}#', '', $pattern); 187 188 return isset($pattern[0]) && false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; 189 } 190 191 /** 192 * Computes the regexp used to match a specific token. It can be static text or a subpattern. 193 * 194 * @param array $tokens The route tokens 195 * @param int $index The index of the current token 196 * @param int $firstOptional The index of the first optional token 197 * 198 * @return string The regexp pattern for a single token 199 */ 200 private static function computeRegexp(array $tokens, $index, $firstOptional) 201 { 202 $token = $tokens[$index]; 203 if ('text' === $token[0]) { 204 // Text tokens 205 return preg_quote($token[1], self::REGEX_DELIMITER); 206 } else { 207 // Variable tokens 208 if (0 === $index && 0 === $firstOptional) { 209 // When the only token is an optional variable token, the separator is required 210 return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); 211 } else { 212 $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); 213 if ($index >= $firstOptional) { 214 // Enclose each optional token in a subpattern to make it optional. 215 // "?:" means it is non-capturing, i.e. the portion of the subject string that 216 // matched the optional subpattern is not passed back. 217 $regexp = "(?:$regexp"; 218 $nbTokens = count($tokens); 219 if ($nbTokens - 1 == $index) { 220 // Close the optional subpatterns 221 $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); 222 } 223 } 224 225 return $regexp; 226 } 227 } 228 } 229 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Jan 11 00:25:41 2018 | Cross-referenced by PHPXref 0.7.1 |