4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Routing\Matcher\Dumper
;
14 use Symfony\Component\Routing\Route
;
17 * Dumps a set of Apache mod_rewrite rules.
19 * @author Fabien Potencier <fabien@symfony.com>
20 * @author Kris Wallsmith <kris@symfony.com>
22 class ApacheMatcherDumper
extends MatcherDumper
25 * Dumps a set of Apache mod_rewrite rules.
29 * * script_name: The script name (app.php by default)
30 * * base_uri: The base URI ("" by default)
32 * @param array $options An array of options
34 * @return string A string to be used as Apache rewrite rules
36 * @throws \LogicException When the route regex is invalid
38 public function dump(array $options = array())
40 $options = array_merge(array(
41 'script_name' => 'app.php',
45 $options['script_name'] = self
::escape($options['script_name'], ' ', '\\');
47 $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]");
48 $methodVars = array();
52 foreach ($this->getRoutes()->all() as $name => $route) {
54 $compiledRoute = $route->compile();
55 $hostRegex = $compiledRoute->getHostRegex();
57 if (null !== $hostRegex && $prevHostRegex !== $hostRegex) {
58 $prevHostRegex = $hostRegex;
63 $regex = $this->regexToApacheRegex($hostRegex);
64 $regex = self
::escape($regex, ' ', '\\');
66 $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex);
69 $variables[] = sprintf('E=__ROUTING_host_%s:1', $hostRegexUnique);
71 foreach ($compiledRoute->getHostVariables() as $i => $variable) {
72 $variables[] = sprintf('E=__ROUTING_host_%s_%s:%%%d', $hostRegexUnique, $variable, $i+
1);
75 $variables = implode(',', $variables);
77 $rule[] = sprintf('RewriteRule .? - [%s]', $variables);
79 $rules[] = implode("\n", $rule);
82 $rules[] = $this->dumpRoute($name, $route, $options, $hostRegexUnique);
84 if ($req = $route->getRequirement('_method')) {
85 $methods = explode('|', strtoupper($req));
86 $methodVars = array_merge($methodVars, $methods);
89 if (0 < count($methodVars)) {
90 $rule = array('# 405 Method Not Allowed');
91 $methodVars = array_values(array_unique($methodVars));
92 if (in_array('GET', $methodVars) && !in_array('HEAD', $methodVars)) {
93 $methodVars[] = 'HEAD';
95 foreach ($methodVars as $i => $methodVar) {
96 $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i +
1]) ? ' [OR]' : '');
98 $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']);
100 $rules[] = implode("\n", $rule);
103 return implode("\n\n", $rules)."\n";
107 * Dumps a single route
109 * @param string $name Route name
110 * @param Route $route The route
111 * @param array $options Options
112 * @param bool $hostRegexUnique Unique identifier for the host regex
114 * @return string The compiled route
116 private function dumpRoute($name, $route, array $options, $hostRegexUnique)
118 $compiledRoute = $route->compile();
120 // prepare the apache regex
121 $regex = $this->regexToApacheRegex($compiledRoute->getRegex());
122 $regex = '^'.self
::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\');
124 $methods = $this->getRouteMethods($route);
126 $hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^
/$' !== $regex;
128 $variables = array('E=_ROUTING_route:'.$name);
129 foreach ($compiledRoute->getHostVariables() as $variable) {
130 $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_host_%s_%s}', $variable, $hostRegexUnique, $variable);
132 foreach ($compiledRoute->getPathVariables() as $i => $variable) {
133 $variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i +
1);
135 foreach ($route->getDefaults() as $key => $value) {
136 $variables[] = 'E=_ROUTING_default_'.$key.':'.strtr($value, array(
143 $variables = implode(',', $variables);
145 $rule = array("# $name");
148 if (0 < count($methods)) {
150 foreach ($methods as $method) {
151 $allow[] = 'E=_ROUTING_allow_'.$method.':1';
154 if ($hostRegex = $compiledRoute->getHostRegex()) {
155 $rule[] = sprintf("RewriteCond %%{ENV
:__ROUTING_host_%s
} =1", $hostRegexUnique);
158 $rule[] = "RewriteCond %{REQUEST_URI
} $regex";
159 $rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]", implode('|', $methods));
160 $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow));
163 // redirect with trailing slash appended
164 if ($hasTrailingSlash) {
166 if ($hostRegex = $compiledRoute->getHostRegex()) {
167 $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
170 $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$';
171 $rule[] = 'RewriteRule
.* $0/ [QSA
,L
,R
=301]';
176 if ($hostRegex = $compiledRoute->getHostRegex()) {
177 $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
180 $rule[] = "RewriteCond %{REQUEST_URI} $regex";
181 $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]";
183 return implode("\n", $rule);
187 * Returns methods allowed for a route
189 * @param Route $route The route
191 * @return array The methods
193 private function getRouteMethods(Route $route)
196 if ($req = $route->getRequirement('_method
')) {
197 $methods = explode('|', strtoupper($req));
198 // GET and HEAD are equivalent
199 if (in_array('GET
', $methods) && !in_array('HEAD
', $methods)) {
208 * Converts a regex to make it suitable for mod_rewrite
210 * @param string $regex The regex
212 * @return string The converted regex
214 private function regexToApacheRegex($regex)
216 $regexPatternEnd = strrpos($regex, $regex[0]);
218 return preg_replace('/\?P
<.+
?>/', '', substr($regex, 1, $regexPatternEnd - 1));
224 * @param string $string The string to be escaped
225 * @param string $char The character to be escaped
226 * @param string $with The character to be used for escaping
228 * @return string The escaped string
230 private static function escape($string, $char, $with)
234 foreach (str_split($string) as $symbol) {
240 if ($symbol === $char) {
241 $output .= $with.$char;
244 if ($symbol === $with) {