ApacheMatcherDumper.php

Namespace

Symfony\Component\Routing\Matcher\Dumper

File

drupal/core/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
View source
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Symfony\Component\Routing\Matcher\Dumper;

use Symfony\Component\Routing\Route;

/**
 * Dumps a set of Apache mod_rewrite rules.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Kris Wallsmith <kris@symfony.com>
 */
class ApacheMatcherDumper extends MatcherDumper {

  /**
   * Dumps a set of Apache mod_rewrite rules.
   *
   * Available options:
   *
   *  * script_name: The script name (app.php by default)
   *  * base_uri:    The base URI ("" by default)
   *
   * @param array $options An array of options
   *
   * @return string A string to be used as Apache rewrite rules
   *
   * @throws \LogicException When the route regex is invalid
   */
  public function dump(array $options = array()) {
    $options = array_merge(array(
      'script_name' => 'app.php',
      'base_uri' => '',
    ), $options);
    $options['script_name'] = self::escape($options['script_name'], ' ', '\\');
    $rules = array(
      "# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]",
    );
    $methodVars = array();
    $hostRegexUnique = 0;
    $prevHostRegex = '';
    foreach ($this
      ->getRoutes()
      ->all() as $name => $route) {
      $compiledRoute = $route
        ->compile();
      $hostRegex = $compiledRoute
        ->getHostRegex();
      if (null !== $hostRegex && $prevHostRegex !== $hostRegex) {
        $prevHostRegex = $hostRegex;
        $hostRegexUnique++;
        $rule = array();
        $regex = $this
          ->regexToApacheRegex($hostRegex);
        $regex = self::escape($regex, ' ', '\\');
        $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex);
        $variables = array();
        $variables[] = sprintf('E=__ROUTING_host_%s:1', $hostRegexUnique);
        foreach ($compiledRoute
          ->getHostVariables() as $i => $variable) {
          $variables[] = sprintf('E=__ROUTING_host_%s_%s:%%%d', $hostRegexUnique, $variable, $i + 1);
        }
        $variables = implode(',', $variables);
        $rule[] = sprintf('RewriteRule .? - [%s]', $variables);
        $rules[] = implode("\n", $rule);
      }
      $rules[] = $this
        ->dumpRoute($name, $route, $options, $hostRegexUnique);
      if ($req = $route
        ->getRequirement('_method')) {
        $methods = explode('|', strtoupper($req));
        $methodVars = array_merge($methodVars, $methods);
      }
    }
    if (0 < count($methodVars)) {
      $rule = array(
        '# 405 Method Not Allowed',
      );
      $methodVars = array_values(array_unique($methodVars));
      if (in_array('GET', $methodVars) && !in_array('HEAD', $methodVars)) {
        $methodVars[] = 'HEAD';
      }
      foreach ($methodVars as $i => $methodVar) {
        $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : '');
      }
      $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']);
      $rules[] = implode("\n", $rule);
    }
    return implode("\n\n", $rules) . "\n";
  }

  /**
   * Dumps a single route
   *
   * @param  string $name Route name
   * @param  Route  $route The route
   * @param  array  $options Options
   * @param  bool   $hostRegexUnique Unique identifier for the host regex
   *
   * @return string The compiled route
   */
  private function dumpRoute($name, $route, array $options, $hostRegexUnique) {
    $compiledRoute = $route
      ->compile();

    // prepare the apache regex
    $regex = $this
      ->regexToApacheRegex($compiledRoute
      ->getRegex());
    $regex = '^' . self::escape(preg_quote($options['base_uri']) . substr($regex, 1), ' ', '\\');
    $methods = $this
      ->getRouteMethods($route);
    $hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex;
    $variables = array(
      'E=_ROUTING_route:' . $name,
    );
    foreach ($compiledRoute
      ->getHostVariables() as $variable) {
      $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_host_%s_%s}', $variable, $hostRegexUnique, $variable);
    }
    foreach ($compiledRoute
      ->getPathVariables() as $i => $variable) {
      $variables[] = 'E=_ROUTING_param_' . $variable . ':%' . ($i + 1);
    }
    foreach ($route
      ->getDefaults() as $key => $value) {
      $variables[] = 'E=_ROUTING_default_' . $key . ':' . strtr($value, array(
        ':' => '\\:',
        '=' => '\\=',
        '\\' => '\\\\',
        ' ' => '\\ ',
      ));
    }
    $variables = implode(',', $variables);
    $rule = array(
      "# {$name}",
    );

    // method mismatch
    if (0 < count($methods)) {
      $allow = array();
      foreach ($methods as $method) {
        $allow[] = 'E=_ROUTING_allow_' . $method . ':1';
      }
      if ($hostRegex = $compiledRoute
        ->getHostRegex()) {
        $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
      }
      $rule[] = "RewriteCond %{REQUEST_URI} {$regex}";
      $rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)\$ [NC]", implode('|', $methods));
      $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow));
    }

    // redirect with trailing slash appended
    if ($hasTrailingSlash) {
      if ($hostRegex = $compiledRoute
        ->getHostRegex()) {
        $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
      }
      $rule[] = 'RewriteCond %{REQUEST_URI} ' . substr($regex, 0, -2) . '$';
      $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]';
    }

    // the main rule
    if ($hostRegex = $compiledRoute
      ->getHostRegex()) {
      $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
    }
    $rule[] = "RewriteCond %{REQUEST_URI} {$regex}";
    $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,{$variables}]";
    return implode("\n", $rule);
  }

  /**
   * Returns methods allowed for a route
   *
   * @param Route  $route The route
   *
   * @return array The methods
   */
  private function getRouteMethods(Route $route) {
    $methods = array();
    if ($req = $route
      ->getRequirement('_method')) {
      $methods = explode('|', strtoupper($req));

      // GET and HEAD are equivalent
      if (in_array('GET', $methods) && !in_array('HEAD', $methods)) {
        $methods[] = 'HEAD';
      }
    }
    return $methods;
  }

  /**
   * Converts a regex to make it suitable for mod_rewrite
   *
   * @param string  $regex The regex
   *
   * @return string The converted regex
   */
  private function regexToApacheRegex($regex) {
    $regexPatternEnd = strrpos($regex, $regex[0]);
    return preg_replace('/\\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1));
  }

  /**
   * Escapes a string.
   *
   * @param string $string The string to be escaped
   * @param string $char   The character to be escaped
   * @param string $with   The character to be used for escaping
   *
   * @return string The escaped string
   */
  private static function escape($string, $char, $with) {
    $escaped = false;
    $output = '';
    foreach (str_split($string) as $symbol) {
      if ($escaped) {
        $output .= $symbol;
        $escaped = false;
        continue;
      }
      if ($symbol === $char) {
        $output .= $with . $char;
        continue;
      }
      if ($symbol === $with) {
        $escaped = true;
      }
      $output .= $symbol;
    }
    return $output;
  }

}

Classes

Namesort descending Description
ApacheMatcherDumper Dumps a set of Apache mod_rewrite rules.