]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
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\Routing\Route; | |
15 | ||
16 | /** | |
17 | * Dumps a set of Apache mod_rewrite rules. | |
18 | * | |
19 | * @author Fabien Potencier <fabien@symfony.com> | |
20 | * @author Kris Wallsmith <kris@symfony.com> | |
21 | */ | |
22 | class ApacheMatcherDumper extends MatcherDumper | |
23 | { | |
24 | /** | |
25 | * Dumps a set of Apache mod_rewrite rules. | |
26 | * | |
27 | * Available options: | |
28 | * | |
29 | * * script_name: The script name (app.php by default) | |
30 | * * base_uri: The base URI ("" by default) | |
31 | * | |
32 | * @param array $options An array of options | |
33 | * | |
34 | * @return string A string to be used as Apache rewrite rules | |
35 | * | |
36 | * @throws \LogicException When the route regex is invalid | |
37 | */ | |
38 | public function dump(array $options = array()) | |
39 | { | |
40 | $options = array_merge(array( | |
41 | 'script_name' => 'app.php', | |
42 | 'base_uri' => '', | |
43 | ), $options); | |
44 | ||
45 | $options['script_name'] = self::escape($options['script_name'], ' ', '\\'); | |
46 | ||
47 | $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]"); | |
48 | $methodVars = array(); | |
49 | $hostRegexUnique = 0; | |
50 | $prevHostRegex = ''; | |
51 | ||
52 | foreach ($this->getRoutes()->all() as $name => $route) { | |
53 | ||
54 | $compiledRoute = $route->compile(); | |
55 | $hostRegex = $compiledRoute->getHostRegex(); | |
56 | ||
57 | if (null !== $hostRegex && $prevHostRegex !== $hostRegex) { | |
58 | $prevHostRegex = $hostRegex; | |
59 | $hostRegexUnique++; | |
60 | ||
61 | $rule = array(); | |
62 | ||
63 | $regex = $this->regexToApacheRegex($hostRegex); | |
64 | $regex = self::escape($regex, ' ', '\\'); | |
65 | ||
66 | $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex); | |
67 | ||
68 | $variables = array(); | |
69 | $variables[] = sprintf('E=__ROUTING_host_%s:1', $hostRegexUnique); | |
70 | ||
71 | foreach ($compiledRoute->getHostVariables() as $i => $variable) { | |
72 | $variables[] = sprintf('E=__ROUTING_host_%s_%s:%%%d', $hostRegexUnique, $variable, $i+1); | |
73 | } | |
74 | ||
75 | $variables = implode(',', $variables); | |
76 | ||
77 | $rule[] = sprintf('RewriteRule .? - [%s]', $variables); | |
78 | ||
79 | $rules[] = implode("\n", $rule); | |
80 | } | |
81 | ||
82 | $rules[] = $this->dumpRoute($name, $route, $options, $hostRegexUnique); | |
83 | ||
84 | if ($req = $route->getRequirement('_method')) { | |
85 | $methods = explode('|', strtoupper($req)); | |
86 | $methodVars = array_merge($methodVars, $methods); | |
87 | } | |
88 | } | |
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'; | |
94 | } | |
95 | foreach ($methodVars as $i => $methodVar) { | |
96 | $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : ''); | |
97 | } | |
98 | $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']); | |
99 | ||
100 | $rules[] = implode("\n", $rule); | |
101 | } | |
102 | ||
103 | return implode("\n\n", $rules)."\n"; | |
104 | } | |
105 | ||
106 | /** | |
107 | * Dumps a single route | |
108 | * | |
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 | |
113 | * | |
114 | * @return string The compiled route | |
115 | */ | |
116 | private function dumpRoute($name, $route, array $options, $hostRegexUnique) | |
117 | { | |
118 | $compiledRoute = $route->compile(); | |
119 | ||
120 | // prepare the apache regex | |
121 | $regex = $this->regexToApacheRegex($compiledRoute->getRegex()); | |
122 | $regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\'); | |
123 | ||
124 | $methods = $this->getRouteMethods($route); | |
125 | ||
126 | $hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex; | |
127 | ||
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); | |
131 | } | |
132 | foreach ($compiledRoute->getPathVariables() as $i => $variable) { | |
133 | $variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1); | |
134 | } | |
135 | foreach ($route->getDefaults() as $key => $value) { | |
136 | $variables[] = 'E=_ROUTING_default_'.$key.':'.strtr($value, array( | |
137 | ':' => '\\:', | |
138 | '=' => '\\=', | |
139 | '\\' => '\\\\', | |
140 | ' ' => '\\ ', | |
141 | )); | |
142 | } | |
143 | $variables = implode(',', $variables); | |
144 | ||
145 | $rule = array("# $name"); | |
146 | ||
147 | // method mismatch | |
148 | if (0 < count($methods)) { | |
149 | $allow = array(); | |
150 | foreach ($methods as $method) { | |
151 | $allow[] = 'E=_ROUTING_allow_'.$method.':1'; | |
152 | } | |
153 | ||
154 | if ($hostRegex = $compiledRoute->getHostRegex()) { | |
155 | $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique); | |
156 | } | |
157 | ||
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)); | |
161 | } | |
162 | ||
163 | // redirect with trailing slash appended | |
164 | if ($hasTrailingSlash) { | |
165 | ||
166 | if ($hostRegex = $compiledRoute->getHostRegex()) { | |
167 | $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique); | |
168 | } | |
169 | ||
170 | $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$'; | |
171 | $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]'; | |
172 | } | |
173 | ||
174 | // the main rule | |
175 | ||
176 | if ($hostRegex = $compiledRoute->getHostRegex()) { | |
177 | $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique); | |
178 | } | |
179 | ||
180 | $rule[] = "RewriteCond %{REQUEST_URI} $regex"; | |
181 | $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]"; | |
182 | ||
183 | return implode("\n", $rule); | |
184 | } | |
185 | ||
186 | /** | |
187 | * Returns methods allowed for a route | |
188 | * | |
189 | * @param Route $route The route | |
190 | * | |
191 | * @return array The methods | |
192 | */ | |
193 | private function getRouteMethods(Route $route) | |
194 | { | |
195 | $methods = array(); | |
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)) { | |
200 | $methods[] = 'HEAD'; | |
201 | } | |
202 | } | |
203 | ||
204 | return $methods; | |
205 | } | |
206 | ||
207 | /** | |
208 | * Converts a regex to make it suitable for mod_rewrite | |
209 | * | |
210 | * @param string $regex The regex | |
211 | * | |
212 | * @return string The converted regex | |
213 | */ | |
214 | private function regexToApacheRegex($regex) | |
215 | { | |
216 | $regexPatternEnd = strrpos($regex, $regex[0]); | |
217 | ||
218 | return preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1)); | |
219 | } | |
220 | ||
221 | /** | |
222 | * Escapes a string. | |
223 | * | |
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 | |
227 | * | |
228 | * @return string The escaped string | |
229 | */ | |
230 | private static function escape($string, $char, $with) | |
231 | { | |
232 | $escaped = false; | |
233 | $output = ''; | |
234 | foreach (str_split($string) as $symbol) { | |
235 | if ($escaped) { | |
236 | $output .= $symbol; | |
237 | $escaped = false; | |
238 | continue; | |
239 | } | |
240 | if ($symbol === $char) { | |
241 | $output .= $with.$char; | |
242 | continue; | |
243 | } | |
244 | if ($symbol === $with) { | |
245 | $escaped = true; | |
246 | } | |
247 | $output .= $symbol; | |
248 | } | |
249 | ||
250 | return $output; | |
251 | } | |
252 | } |