You are here

class ApacheMatcherDumper in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php \Symfony\Component\Routing\Matcher\Dumper\ApacheMatcherDumper

Dumps a set of Apache mod_rewrite rules.

@author Fabien Potencier <fabien@symfony.com> @author Kris Wallsmith <kris@symfony.com>

Hierarchy

Expanded class hierarchy of ApacheMatcherDumper

Deprecated

since version 2.5, to be removed in 3.0. The performance gains are minimal and it's very hard to replicate the behavior of PHP implementation.

1 file declares its use of ApacheMatcherDumper
LegacyApacheMatcherDumperTest.php in vendor/symfony/routing/Tests/Matcher/Dumper/LegacyApacheMatcherDumperTest.php

File

vendor/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php, line 28

Namespace

Symfony\Component\Routing\Matcher\Dumper
View source
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) {
      if ($route
        ->getCondition()) {
        throw new \LogicException(sprintf('Unable to dump the routes for Apache as route "%s" has a condition.', $name));
      }
      $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);
      $methodVars = array_merge($methodVars, $route
        ->getMethods());
    }
    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 ($this
      ->normalizeValues($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 ($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 ($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 ($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 = $route
      ->getMethods();

    // 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;
  }

  /**
   * Normalizes an array of values.
   *
   * @param array $values
   *
   * @return string[]
   */
  private function normalizeValues(array $values) {
    $normalizedValues = array();
    foreach ($values as $key => $value) {
      if (is_array($value)) {
        foreach ($value as $index => $bit) {
          $normalizedValues[sprintf('%s[%s]', $key, $index)] = $bit;
        }
      }
      else {
        $normalizedValues[$key] = (string) $value;
      }
    }
    return $normalizedValues;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ApacheMatcherDumper::dump public function Dumps a set of Apache mod_rewrite rules. Overrides MatcherDumperInterface::dump
ApacheMatcherDumper::dumpRoute private function Dumps a single route.
ApacheMatcherDumper::escape private static function Escapes a string.
ApacheMatcherDumper::getRouteMethods private function Returns methods allowed for a route.
ApacheMatcherDumper::normalizeValues private function Normalizes an array of values.
ApacheMatcherDumper::regexToApacheRegex private function Converts a regex to make it suitable for mod_rewrite.
MatcherDumper::$routes private property
MatcherDumper::getRoutes public function Gets the routes to dump. Overrides MatcherDumperInterface::getRoutes
MatcherDumper::__construct public function Constructor.