Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

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: 02.04.2025, 15:02 - Dateigröße: 13.69 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       * The maximum supported length of a PCRE subpattern name
033       * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16.
034       *
035       * @internal
036       */
037      const VARIABLE_MAXIMUM_LENGTH = 32;
038   
039      /**
040       * {@inheritdoc}
041       *
042       * @throws \InvalidArgumentException if a path variable is named _fragment
043       * @throws \LogicException           if a variable is referenced more than once
044       * @throws \DomainException          if a variable name starts with a digit or if it is too long to be successfully used as
045       *                                   a PCRE subpattern
046       */
047      public static function compile(Route $route)
048      {
049          $hostVariables = [];
050          $variables = [];
051          $hostRegex = null;
052          $hostTokens = [];
053   
054          if ('' !== $host = $route->getHost()) {
055              $result = self::compilePattern($route, $host, true);
056   
057              $hostVariables = $result['variables'];
058              $variables = $hostVariables;
059   
060              $hostTokens = $result['tokens'];
061              $hostRegex = $result['regex'];
062          }
063   
064          $path = $route->getPath();
065   
066          $result = self::compilePattern($route, $path, false);
067   
068          $staticPrefix = $result['staticPrefix'];
069   
070          $pathVariables = $result['variables'];
071   
072          foreach ($pathVariables as $pathParam) {
073              if ('_fragment' === $pathParam) {
074                  throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath()));
075              }
076          }
077   
078          $variables = array_merge($variables, $pathVariables);
079   
080          $tokens = $result['tokens'];
081          $regex = $result['regex'];
082   
083          return new CompiledRoute(
084              $staticPrefix,
085              $regex,
086              $tokens,
087              $pathVariables,
088              $hostRegex,
089              $hostTokens,
090              $hostVariables,
091              array_unique($variables)
092          );
093      }
094   
095      private static function compilePattern(Route $route, $pattern, $isHost)
096      {
097          $tokens = [];
098          $variables = [];
099          $matches = [];
100          $pos = 0;
101          $defaultSeparator = $isHost ? '.' : '/';
102          $useUtf8 = preg_match('//u', $pattern);
103          $needsUtf8 = $route->getOption('utf8');
104   
105          if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) {
106              $needsUtf8 = true;
107              @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), \E_USER_DEPRECATED);
108          }
109          if (!$useUtf8 && $needsUtf8) {
110              throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern));
111          }
112   
113          // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
114          // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
115          preg_match_all('#\{\w+\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
116          foreach ($matches as $match) {
117              $varName = substr($match[0][0], 1, -1);
118              // get all static text preceding the current variable
119              $precedingText = substr($pattern, $pos, $match[0][1] - $pos);
120              $pos = $match[0][1] + \strlen($match[0][0]);
121   
122              if (!\strlen($precedingText)) {
123                  $precedingChar = '';
124              } elseif ($useUtf8) {
125                  preg_match('/.$/u', $precedingText, $precedingChar);
126                  $precedingChar = $precedingChar[0];
127              } else {
128                  $precedingChar = substr($precedingText, -1);
129              }
130              $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar);
131   
132              // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the
133              // variable would not be usable as a Controller action argument.
134              if (preg_match('/^\d/', $varName)) {
135                  throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern));
136              }
137              if (\in_array($varName, $variables)) {
138                  throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName));
139              }
140   
141              if (\strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) {
142                  throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %d characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
143              }
144   
145              if ($isSeparator && $precedingText !== $precedingChar) {
146                  $tokens[] = ['text', substr($precedingText, 0, -\strlen($precedingChar))];
147              } elseif (!$isSeparator && \strlen($precedingText) > 0) {
148                  $tokens[] = ['text', $precedingText];
149              }
150   
151              $regexp = $route->getRequirement($varName);
152              if (null === $regexp) {
153                  $followingPattern = (string) substr($pattern, $pos);
154                  // Find the next static character after the variable that functions as a separator. By default, this separator and '/'
155                  // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all
156                  // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are
157                  // the same that will be matched. Example: new Route('/{page}.{_format}', ['_format' => 'html'])
158                  // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything.
159                  // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally
160                  // part of {_format} when generating the URL, e.g. _format = 'mobile.html'.
161                  $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8);
162                  $regexp = sprintf(
163                      '[^%s%s]+',
164                      preg_quote($defaultSeparator, self::REGEX_DELIMITER),
165                      $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : ''
166                  );
167                  if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
168                      // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
169                      // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
170                      // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
171                      // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is
172                      // directly adjacent, e.g. '/{x}{y}'.
173                      $regexp .= '+';
174                  }
175              } else {
176                  if (!preg_match('//u', $regexp)) {
177                      $useUtf8 = false;
178                  } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?<!\\\\)\\\\(?:\\\\\\\\)*+(?-i:X|[pP][\{CLMNPSZ]|x\{[A-Fa-f0-9]{3})/', $regexp)) {
179                      $needsUtf8 = true;
180                      @trigger_error(sprintf('Using UTF-8 route requirements without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for variable "%s" in pattern "%s".', $varName, $pattern), \E_USER_DEPRECATED);
181                  }
182                  if (!$useUtf8 && $needsUtf8) {
183                      throw new \LogicException(sprintf('Cannot mix UTF-8 requirement with non-UTF-8 charset for variable "%s" in pattern "%s".', $varName, $pattern));
184                  }
185              }
186   
187              $tokens[] = ['variable', $isSeparator ? $precedingChar : '', $regexp, $varName];
188              $variables[] = $varName;
189          }
190   
191          if ($pos < \strlen($pattern)) {
192              $tokens[] = ['text', substr($pattern, $pos)];
193          }
194   
195          // find the first optional token
196          $firstOptional = \PHP_INT_MAX;
197          if (!$isHost) {
198              for ($i = \count($tokens) - 1; $i >= 0; --$i) {
199                  $token = $tokens[$i];
200                  if ('variable' === $token[0] && $route->hasDefault($token[3])) {
201                      $firstOptional = $i;
202                  } else {
203                      break;
204                  }
205              }
206          }
207   
208          // compute the matching regexp
209          $regexp = '';
210          for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) {
211              $regexp .= self::computeRegexp($tokens, $i, $firstOptional);
212          }
213          $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'sD'.($isHost ? 'i' : '');
214   
215          // enable Utf8 matching if really required
216          if ($needsUtf8) {
217              $regexp .= 'u';
218              for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) {
219                  if ('variable' === $tokens[$i][0]) {
220                      $tokens[$i][] = true;
221                  }
222              }
223          }
224   
225          return [
226              'staticPrefix' => self::determineStaticPrefix($route, $tokens),
227              'regex' => $regexp,
228              'tokens' => array_reverse($tokens),
229              'variables' => $variables,
230          ];
231      }
232   
233      /**
234       * Determines the longest static prefix possible for a route.
235       *
236       * @return string The leading static part of a route's path
237       */
238      private static function determineStaticPrefix(Route $route, array $tokens)
239      {
240          if ('text' !== $tokens[0][0]) {
241              return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1];
242          }
243   
244          $prefix = $tokens[0][1];
245   
246          if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && false === $route->hasDefault($tokens[1][3])) {
247              $prefix .= $tokens[1][1];
248          }
249   
250          return $prefix;
251      }
252   
253      /**
254       * Returns the next static character in the Route pattern that will serve as a separator.
255       *
256       * @param string $pattern The route pattern
257       * @param bool   $useUtf8 Whether the character is encoded in UTF-8 or not
258       *
259       * @return string The next static character that functions as separator (or empty string when none available)
260       */
261      private static function findNextSeparator($pattern, $useUtf8)
262      {
263          if ('' == $pattern) {
264              // return empty string if pattern is empty or false (false which can be returned by substr)
265              return '';
266          }
267          // first remove all placeholders from the pattern so we can find the next real static character
268          if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) {
269              return '';
270          }
271          if ($useUtf8) {
272              preg_match('/^./u', $pattern, $pattern);
273          }
274   
275          return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : '';
276      }
277   
278      /**
279       * Computes the regexp used to match a specific token. It can be static text or a subpattern.
280       *
281       * @param array $tokens        The route tokens
282       * @param int   $index         The index of the current token
283       * @param int   $firstOptional The index of the first optional token
284       *
285       * @return string The regexp pattern for a single token
286       */
287      private static function computeRegexp(array $tokens, $index, $firstOptional)
288      {
289          $token = $tokens[$index];
290          if ('text' === $token[0]) {
291              // Text tokens
292              return preg_quote($token[1], self::REGEX_DELIMITER);
293          } else {
294              // Variable tokens
295              if (0 === $index && 0 === $firstOptional) {
296                  // When the only token is an optional variable token, the separator is required
297                  return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
298              } else {
299                  $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
300                  if ($index >= $firstOptional) {
301                      // Enclose each optional token in a subpattern to make it optional.
302                      // "?:" means it is non-capturing, i.e. the portion of the subject string that
303                      // matched the optional subpattern is not passed back.
304                      $regexp = "(?:$regexp";
305                      $nbTokens = \count($tokens);
306                      if ($nbTokens - 1 == $index) {
307                          // Close the optional subpatterns
308                          $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0));
309                      }
310                  }
311   
312                  return $regexp;
313              }
314          }
315      }
316  }
317