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