ApacheMatcherDumper.php 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing\Matcher\Dumper;
  11. /**
  12. * Dumps a set of Apache mod_rewrite rules.
  13. *
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. * @author Kris Wallsmith <kris@symfony.com>
  16. */
  17. class ApacheMatcherDumper extends MatcherDumper
  18. {
  19. /**
  20. * Dumps a set of Apache mod_rewrite rules.
  21. *
  22. * Available options:
  23. *
  24. * * script_name: The script name (app.php by default)
  25. * * base_uri: The base URI ("" by default)
  26. *
  27. * @param array $options An array of options
  28. *
  29. * @return string A string to be used as Apache rewrite rules
  30. *
  31. * @throws \LogicException When the route regex is invalid
  32. */
  33. public function dump(array $options = array())
  34. {
  35. $options = array_merge(array(
  36. 'script_name' => 'app.php',
  37. 'base_uri' => '',
  38. ), $options);
  39. $options['script_name'] = self::escape($options['script_name'], ' ', '\\');
  40. $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]");
  41. $methodVars = array();
  42. foreach ($this->getRoutes()->all() as $name => $route) {
  43. $compiledRoute = $route->compile();
  44. // prepare the apache regex
  45. $regex = $compiledRoute->getRegex();
  46. $delimiter = $regex[0];
  47. $regexPatternEnd = strrpos($regex, $delimiter);
  48. if (strlen($regex) < 2 || 0 === $regexPatternEnd) {
  49. throw new \LogicException('The "%s" route regex "%s" is invalid', $name, $regex);
  50. }
  51. $regex = preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1));
  52. $regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\');
  53. $hasTrailingSlash = '/$' == substr($regex, -2) && '^/$' != $regex;
  54. $variables = array('E=_ROUTING__route:'.$name);
  55. foreach ($compiledRoute->getVariables() as $i => $variable) {
  56. $variables[] = 'E=_ROUTING_'.$variable.':%'.($i + 1);
  57. }
  58. foreach ($route->getDefaults() as $key => $value) {
  59. $variables[] = 'E=_ROUTING_'.$key.':'.strtr($value, array(
  60. ':' => '\\:',
  61. '=' => '\\=',
  62. '\\' => '\\\\',
  63. ' ' => '\\ ',
  64. ));
  65. }
  66. $variables = implode(',', $variables);
  67. $rule = array("# $name");
  68. // method mismatch
  69. if ($req = $route->getRequirement('_method')) {
  70. $methods = explode('|', strtoupper($req));
  71. // GET and HEAD are equivalent
  72. if (in_array('GET', $methods) && !in_array('HEAD', $methods)) {
  73. $methods[] = 'HEAD';
  74. }
  75. $allow = array();
  76. foreach ($methods as $method) {
  77. $methodVars[] = $method;
  78. $allow[] = 'E=_ROUTING__allow_'.$method.':1';
  79. }
  80. $rule[] = "RewriteCond %{REQUEST_URI} $regex";
  81. $rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]", implode('|', $methods));
  82. $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow));
  83. }
  84. // redirect with trailing slash appended
  85. if ($hasTrailingSlash) {
  86. $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$';
  87. $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]';
  88. }
  89. // the main rule
  90. $rule[] = "RewriteCond %{REQUEST_URI} $regex";
  91. $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]";
  92. $rules[] = implode("\n", $rule);
  93. }
  94. if (0 < count($methodVars)) {
  95. $rule = array('# 405 Method Not Allowed');
  96. $methodVars = array_values(array_unique($methodVars));
  97. foreach ($methodVars as $i => $methodVar) {
  98. $rule[] = sprintf('RewriteCond %%{_ROUTING__allow_%s} !-z%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : '');
  99. }
  100. $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']);
  101. $rules[] = implode("\n", $rule);
  102. }
  103. return implode("\n\n", $rules)."\n";
  104. }
  105. /**
  106. * Escapes a string.
  107. *
  108. * @param string $string The string to be escaped
  109. * @param string $char The character to be escaped
  110. * @param string $with The character to be used for escaping
  111. *
  112. * @return string The escaped string
  113. */
  114. static private function escape($string, $char, $with)
  115. {
  116. $escaped = false;
  117. $output = '';
  118. foreach(str_split($string) as $symbol) {
  119. if ($escaped) {
  120. $output .= $symbol;
  121. $escaped = false;
  122. continue;
  123. }
  124. if ($symbol === $char) {
  125. $output .= $with.$char;
  126. continue;
  127. }
  128. if ($symbol === $with) {
  129. $escaped = true;
  130. }
  131. $output .= $symbol;
  132. }
  133. return $output;
  134. }
  135. }