Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

RouteCompiler.php

Zuletzt modifiziert: 09.10.2024, 12:54 - Dateigröße: 9.84 KiB


001  <?php
002   
003  /*
004   * This file is part of the Symfony package.
005   *
006   * (c) Fabien Potencier <fabien@symfony.com>
007   *
008   * For the full copyright and license information, please view the LICENSE
009   * file that was distributed with this source code.
010   */
011   
012  namespace Symfony\Component\Routing;
013   
014  /**
015   * RouteCompiler compiles Route instances to CompiledRoute instances.
016   *
017   * @author Fabien Potencier <fabien@symfony.com>
018   * @author Tobias Schultze <http://tobion.de>
019   */
020  class RouteCompiler implements RouteCompilerInterface
021  {
022      const REGEX_DELIMITER = '#';
023   
024      /**
025       * This string defines the characters that are automatically considered separators in front of
026       * optional placeholders (with default and no static text following). Such a single separator
027       * can be left out together with the optional placeholder from matching and generating URLs.
028       */
029      const SEPARATORS = '/,;.:-_~+*=@|';
030   
031      /**
032       * {@inheritdoc}
033       *
034       * @throws \LogicException  If a variable is referenced more than once
035       * @throws \DomainException If a variable name is numeric because PHP raises an error for such
036       *                          subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)".
037       */
038      public static function compile(Route $route)
039      {
040          $hostVariables = array();
041          $variables = array();
042          $hostRegex = null;
043          $hostTokens = array();
044   
045          if ('' !== $host = $route->getHost()) {
046              $result = self::compilePattern($route, $host, true);
047   
048              $hostVariables = $result['variables'];
049              $variables = $hostVariables;
050   
051              $hostTokens = $result['tokens'];
052              $hostRegex = $result['regex'];
053          }
054   
055          $path = $route->getPath();
056   
057          $result = self::compilePattern($route, $path, false);
058   
059          $staticPrefix = $result['staticPrefix'];
060   
061          $pathVariables = $result['variables'];
062          $variables = array_merge($variables, $pathVariables);
063   
064          $tokens = $result['tokens'];
065          $regex = $result['regex'];
066   
067          return new CompiledRoute(
068              $staticPrefix,
069              $regex,
070              $tokens,
071              $pathVariables,
072              $hostRegex,
073              $hostTokens,
074              $hostVariables,
075              array_unique($variables)
076          );
077      }
078   
079      private static function compilePattern(Route $route, $pattern, $isHost)
080      {
081          $tokens = array();
082          $variables = array();
083          $matches = array();
084          $pos = 0;
085          $defaultSeparator = $isHost ? '.' : '/';
086   
087          // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
088          // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
089          preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
090          foreach ($matches as $match) {
091              $varName = substr($match[0][0], 1, -1);
092              // get all static text preceding the current variable
093              $precedingText = substr($pattern, $pos, $match[0][1] - $pos);
094              $pos = $match[0][1] + strlen($match[0][0]);
095              $precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : '';
096              $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar);
097   
098              if (is_numeric($varName)) {
099                  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  }
230