[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/symfony/routing/Matcher/Dumper/ -> PhpMatcherDumper.php (source)

   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\Matcher\Dumper;
  13  
  14  use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
  15  use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  16  use Symfony\Component\Routing\Route;
  17  use Symfony\Component\Routing\RouteCollection;
  18  
  19  /**
  20   * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
  21   *
  22   * @author Fabien Potencier <fabien@symfony.com>
  23   * @author Tobias Schultze <http://tobion.de>
  24   * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
  25   */
  26  class PhpMatcherDumper extends MatcherDumper
  27  {
  28      private $expressionLanguage;
  29  
  30      /**
  31       * @var ExpressionFunctionProviderInterface[]
  32       */
  33      private $expressionLanguageProviders = array();
  34  
  35      /**
  36       * Dumps a set of routes to a PHP class.
  37       *
  38       * Available options:
  39       *
  40       *  * class:      The class name
  41       *  * base_class: The base class name
  42       *
  43       * @param array $options An array of options
  44       *
  45       * @return string A PHP class representing the matcher class
  46       */
  47      public function dump(array $options = array())
  48      {
  49          $options = array_replace(array(
  50              'class' => 'ProjectUrlMatcher',
  51              'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
  52          ), $options);
  53  
  54          // trailing slash support is only enabled if we know how to redirect the user
  55          $interfaces = class_implements($options['base_class']);
  56          $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']);
  57  
  58          return <<<EOF
  59  <?php
  60  
  61  use Symfony\Component\Routing\Exception\MethodNotAllowedException;
  62  use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  63  use Symfony\Component\Routing\RequestContext;
  64  
  65  /**
  66   * This class has been auto-generated
  67   * by the Symfony Routing Component.
  68   */
  69  class {$options['class']} extends {$options['base_class']}
  70  {
  71      public function __construct(RequestContext \$context)
  72      {
  73          \$this->context = \$context;
  74      }
  75  
  76  {$this->generateMatchMethod($supportsRedirections)}
  77  }
  78  
  79  EOF;
  80      }
  81  
  82      public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
  83      {
  84          $this->expressionLanguageProviders[] = $provider;
  85      }
  86  
  87      /**
  88       * Generates the code for the match method implementing UrlMatcherInterface.
  89       *
  90       * @param bool $supportsRedirections Whether redirections are supported by the base class
  91       *
  92       * @return string Match method as PHP code
  93       */
  94      private function generateMatchMethod($supportsRedirections)
  95      {
  96          $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n");
  97  
  98          return <<<EOF
  99      public function match(\$rawPathinfo)
 100      {
 101          \$allow = array();
 102          \$pathinfo = rawurldecode(\$rawPathinfo);
 103          \$context = \$this->context;
 104          \$request = \$this->request ?: \$this->createRequest(\$pathinfo);
 105  
 106  $code
 107  
 108          throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException();
 109      }
 110  EOF;
 111      }
 112  
 113      /**
 114       * Generates PHP code to match a RouteCollection with all its routes.
 115       *
 116       * @param RouteCollection $routes               A RouteCollection instance
 117       * @param bool            $supportsRedirections Whether redirections are supported by the base class
 118       *
 119       * @return string PHP code
 120       */
 121      private function compileRoutes(RouteCollection $routes, $supportsRedirections)
 122      {
 123          $fetchedHost = false;
 124  
 125          $groups = $this->groupRoutesByHostRegex($routes);
 126          $code = '';
 127  
 128          foreach ($groups as $collection) {
 129              if (null !== $regex = $collection->getAttribute('host_regex')) {
 130                  if (!$fetchedHost) {
 131                      $code .= "        \$host = \$this->context->getHost();\n\n";
 132                      $fetchedHost = true;
 133                  }
 134  
 135                  $code .= sprintf("        if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true));
 136              }
 137  
 138              $tree = $this->buildPrefixTree($collection);
 139              $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections);
 140  
 141              if (null !== $regex) {
 142                  // apply extra indention at each line (except empty ones)
 143                  $groupCode = preg_replace('/^.{2,}$/m', '    $0', $groupCode);
 144                  $code .= $groupCode;
 145                  $code .= "        }\n\n";
 146              } else {
 147                  $code .= $groupCode;
 148              }
 149          }
 150  
 151          return $code;
 152      }
 153  
 154      /**
 155       * Generates PHP code recursively to match a tree of routes.
 156       *
 157       * @param DumperPrefixCollection $collection           A DumperPrefixCollection instance
 158       * @param bool                   $supportsRedirections Whether redirections are supported by the base class
 159       * @param string                 $parentPrefix         Prefix of the parent collection
 160       *
 161       * @return string PHP code
 162       */
 163      private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '')
 164      {
 165          $code = '';
 166          $prefix = $collection->getPrefix();
 167          $optimizable = 1 < \strlen($prefix) && 1 < \count($collection->all());
 168          $optimizedPrefix = $parentPrefix;
 169  
 170          if ($optimizable) {
 171              $optimizedPrefix = $prefix;
 172  
 173              $code .= sprintf("    if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true));
 174          }
 175  
 176          foreach ($collection as $route) {
 177              if ($route instanceof DumperCollection) {
 178                  $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix);
 179              } else {
 180                  $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n";
 181              }
 182          }
 183  
 184          if ($optimizable) {
 185              $code .= "    }\n\n";
 186              // apply extra indention at each line (except empty ones)
 187              $code = preg_replace('/^.{2,}$/m', '    $0', $code);
 188          }
 189  
 190          return $code;
 191      }
 192  
 193      /**
 194       * Compiles a single Route to PHP code used to match it against the path info.
 195       *
 196       * @param Route       $route                A Route instance
 197       * @param string      $name                 The name of the Route
 198       * @param bool        $supportsRedirections Whether redirections are supported by the base class
 199       * @param string|null $parentPrefix         The prefix of the parent collection used to optimize the code
 200       *
 201       * @return string PHP code
 202       *
 203       * @throws \LogicException
 204       */
 205      private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null)
 206      {
 207          $code = '';
 208          $compiledRoute = $route->compile();
 209          $conditions = array();
 210          $hasTrailingSlash = false;
 211          $matches = false;
 212          $hostMatches = false;
 213          $methods = $route->getMethods();
 214  
 215          // GET and HEAD are equivalent
 216          if (\in_array('GET', $methods) && !\in_array('HEAD', $methods)) {
 217              $methods[] = 'HEAD';
 218          }
 219  
 220          $supportsTrailingSlash = $supportsRedirections && (!$methods || \in_array('GET', $methods));
 221  
 222          if (!\count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
 223              if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) {
 224                  $conditions[] = sprintf("%s === rtrim(\$pathinfo, '/')", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
 225                  $hasTrailingSlash = true;
 226              } else {
 227                  $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true));
 228              }
 229          } else {
 230              if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) {
 231                  $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true));
 232              }
 233  
 234              $regex = $compiledRoute->getRegex();
 235              if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) {
 236                  $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
 237                  $hasTrailingSlash = true;
 238              }
 239              $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true));
 240  
 241              $matches = true;
 242          }
 243  
 244          if ($compiledRoute->getHostVariables()) {
 245              $hostMatches = true;
 246          }
 247  
 248          if ($route->getCondition()) {
 249              $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
 250          }
 251  
 252          $conditions = implode(' && ', $conditions);
 253  
 254          $code .= <<<EOF
 255          // $name
 256          if ($conditions) {
 257  
 258  EOF;
 259  
 260          $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
 261  
 262          if ($hasTrailingSlash) {
 263              $code .= <<<EOF
 264              if ('/' === substr(\$pathinfo, -1)) {
 265                  // no-op
 266              } elseif (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) {
 267                  goto $gotoname;
 268              } else {
 269                  return \$this->redirect(\$rawPathinfo.'/', '$name');
 270              }
 271  
 272  
 273  EOF;
 274          }
 275  
 276          if ($schemes = $route->getSchemes()) {
 277              if (!$supportsRedirections) {
 278                  throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
 279              }
 280              $schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
 281              if ($methods) {
 282                  $methods = implode("', '", $methods);
 283                  $code .= <<<EOF
 284              \$requiredSchemes = $schemes;
 285              \$hasRequiredScheme = isset(\$requiredSchemes[\$this->context->getScheme()]);
 286              if (!in_array(\$this->context->getMethod(), array('$methods'))) {
 287                  if (\$hasRequiredScheme) {
 288                      \$allow = array_merge(\$allow, array('$methods'));
 289                  }
 290                  goto $gotoname;
 291              }
 292              if (!\$hasRequiredScheme) {
 293                  if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) {
 294                      goto $gotoname;
 295                  }
 296  
 297                  return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes));
 298              }
 299  
 300  
 301  EOF;
 302              } else {
 303                  $code .= <<<EOF
 304              \$requiredSchemes = $schemes;
 305              if (!isset(\$requiredSchemes[\$this->context->getScheme()])) {
 306                  if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) {
 307                      goto $gotoname;
 308                  }
 309  
 310                  return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes));
 311              }
 312  
 313  
 314  EOF;
 315              }
 316          } elseif ($methods) {
 317              if (1 === \count($methods)) {
 318                  $code .= <<<EOF
 319              if (\$this->context->getMethod() != '$methods[0]') {
 320                  \$allow[] = '$methods[0]';
 321                  goto $gotoname;
 322              }
 323  
 324  
 325  EOF;
 326              } else {
 327                  $methods = implode("', '", $methods);
 328                  $code .= <<<EOF
 329              if (!in_array(\$this->context->getMethod(), array('$methods'))) {
 330                  \$allow = array_merge(\$allow, array('$methods'));
 331                  goto $gotoname;
 332              }
 333  
 334  
 335  EOF;
 336              }
 337          }
 338  
 339          // optimize parameters array
 340          if ($matches || $hostMatches) {
 341              $vars = array();
 342              if ($hostMatches) {
 343                  $vars[] = '$hostMatches';
 344              }
 345              if ($matches) {
 346                  $vars[] = '$matches';
 347              }
 348              $vars[] = "array('_route' => '$name')";
 349  
 350              $code .= sprintf(
 351                  "            return \$this->mergeDefaults(array_replace(%s), %s);\n",
 352                  implode(', ', $vars),
 353                  str_replace("\n", '', var_export($route->getDefaults(), true))
 354              );
 355          } elseif ($route->getDefaults()) {
 356              $code .= sprintf("            return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
 357          } else {
 358              $code .= sprintf("            return array('_route' => '%s');\n", $name);
 359          }
 360          $code .= "        }\n";
 361  
 362          if ($hasTrailingSlash || $schemes || $methods) {
 363              $code .= "        $gotoname:\n";
 364          }
 365  
 366          return $code;
 367      }
 368  
 369      /**
 370       * Groups consecutive routes having the same host regex.
 371       *
 372       * The result is a collection of collections of routes having the same host regex.
 373       *
 374       * @param RouteCollection $routes A flat RouteCollection
 375       *
 376       * @return DumperCollection A collection with routes grouped by host regex in sub-collections
 377       */
 378      private function groupRoutesByHostRegex(RouteCollection $routes)
 379      {
 380          $groups = new DumperCollection();
 381  
 382          $currentGroup = new DumperCollection();
 383          $currentGroup->setAttribute('host_regex', null);
 384          $groups->add($currentGroup);
 385  
 386          foreach ($routes as $name => $route) {
 387              $hostRegex = $route->compile()->getHostRegex();
 388              if ($currentGroup->getAttribute('host_regex') !== $hostRegex) {
 389                  $currentGroup = new DumperCollection();
 390                  $currentGroup->setAttribute('host_regex', $hostRegex);
 391                  $groups->add($currentGroup);
 392              }
 393              $currentGroup->add(new DumperRoute($name, $route));
 394          }
 395  
 396          return $groups;
 397      }
 398  
 399      /**
 400       * Organizes the routes into a prefix tree.
 401       *
 402       * Routes order is preserved such that traversing the tree will traverse the
 403       * routes in the origin order.
 404       *
 405       * @return DumperPrefixCollection
 406       */
 407      private function buildPrefixTree(DumperCollection $collection)
 408      {
 409          $tree = new DumperPrefixCollection();
 410          $current = $tree;
 411  
 412          foreach ($collection as $route) {
 413              $current = $current->addPrefixRoute($route);
 414          }
 415  
 416          $tree->mergeSlashNodes();
 417  
 418          return $tree;
 419      }
 420  
 421      private function getExpressionLanguage()
 422      {
 423          if (null === $this->expressionLanguage) {
 424              if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
 425                  throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
 426              }
 427              $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
 428          }
 429  
 430          return $this->expressionLanguage;
 431      }
 432  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1