|
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 use Symfony\Component\Routing\RouteCollection; |
|
16 |
|
17 /** |
|
18 * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. |
|
19 * |
|
20 * @author Fabien Potencier <fabien@symfony.com> |
|
21 */ |
|
22 class PhpMatcherDumper extends MatcherDumper |
|
23 { |
|
24 /** |
|
25 * Dumps a set of routes to a PHP class. |
|
26 * |
|
27 * Available options: |
|
28 * |
|
29 * * class: The class name |
|
30 * * base_class: The base class name |
|
31 * |
|
32 * @param array $options An array of options |
|
33 * |
|
34 * @return string A PHP class representing the matcher class |
|
35 */ |
|
36 public function dump(array $options = array()) |
|
37 { |
|
38 $options = array_merge(array( |
|
39 'class' => 'ProjectUrlMatcher', |
|
40 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', |
|
41 ), $options); |
|
42 |
|
43 // trailing slash support is only enabled if we know how to redirect the user |
|
44 $interfaces = class_implements($options['base_class']); |
|
45 $supportsRedirections = isset($interfaces['Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface']); |
|
46 |
|
47 return |
|
48 $this->startClass($options['class'], $options['base_class']). |
|
49 $this->addConstructor(). |
|
50 $this->addMatcher($supportsRedirections). |
|
51 $this->endClass() |
|
52 ; |
|
53 } |
|
54 |
|
55 private function addMatcher($supportsRedirections) |
|
56 { |
|
57 $code = implode("\n", $this->compileRoutes($this->getRoutes(), $supportsRedirections)); |
|
58 |
|
59 return <<<EOF |
|
60 |
|
61 public function match(\$pathinfo) |
|
62 { |
|
63 \$allow = array(); |
|
64 \$pathinfo = urldecode(\$pathinfo); |
|
65 |
|
66 $code |
|
67 throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); |
|
68 } |
|
69 |
|
70 EOF; |
|
71 } |
|
72 |
|
73 private function compileRoutes(RouteCollection $routes, $supportsRedirections, $parentPrefix = null) |
|
74 { |
|
75 $code = array(); |
|
76 |
|
77 $routes = clone $routes; |
|
78 $routeIterator = $routes->getIterator(); |
|
79 $keys = array_keys($routeIterator->getArrayCopy()); |
|
80 $keysCount = count($keys); |
|
81 |
|
82 $i = 0; |
|
83 foreach ($routeIterator as $name => $route) { |
|
84 $i++; |
|
85 |
|
86 $route = clone $route; |
|
87 if ($route instanceof RouteCollection) { |
|
88 $prefix = $route->getPrefix(); |
|
89 $optimizable = $prefix && count($route->all()) > 1 && false === strpos($route->getPrefix(), '{'); |
|
90 $indent = ''; |
|
91 if ($optimizable) { |
|
92 for ($j = $i; $j < $keysCount; $j++) { |
|
93 if ($keys[$j] === null) { |
|
94 continue; |
|
95 } |
|
96 |
|
97 $testRoute = $routeIterator->offsetGet($keys[$j]); |
|
98 $isCollection = ($testRoute instanceof RouteCollection); |
|
99 |
|
100 $testPrefix = $isCollection ? $testRoute->getPrefix() : $testRoute->getPattern(); |
|
101 |
|
102 if (0 === strpos($testPrefix, $prefix)) { |
|
103 $routeIterator->offsetUnset($keys[$j]); |
|
104 |
|
105 if ($isCollection) { |
|
106 $route->addCollection($testRoute); |
|
107 } else { |
|
108 $route->add($keys[$j], $testRoute); |
|
109 } |
|
110 |
|
111 $i++; |
|
112 $keys[$j] = null; |
|
113 } |
|
114 } |
|
115 |
|
116 if ($prefix !== $parentPrefix) { |
|
117 $code[] = sprintf(" if (0 === strpos(\$pathinfo, %s)) {", var_export($prefix, true)); |
|
118 $indent = ' '; |
|
119 } |
|
120 } |
|
121 |
|
122 foreach ($this->compileRoutes($route, $supportsRedirections, $prefix) as $line) { |
|
123 foreach (explode("\n", $line) as $l) { |
|
124 if ($l) { |
|
125 $code[] = $indent.$l; |
|
126 } else { |
|
127 $code[] = $l; |
|
128 } |
|
129 } |
|
130 } |
|
131 |
|
132 if ($optimizable && $prefix !== $parentPrefix) { |
|
133 $code[] = " }\n"; |
|
134 } |
|
135 } else { |
|
136 foreach ($this->compileRoute($route, $name, $supportsRedirections, $parentPrefix) as $line) { |
|
137 $code[] = $line; |
|
138 } |
|
139 } |
|
140 } |
|
141 |
|
142 return $code; |
|
143 } |
|
144 |
|
145 private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) |
|
146 { |
|
147 $code = array(); |
|
148 $compiledRoute = $route->compile(); |
|
149 $conditions = array(); |
|
150 $hasTrailingSlash = false; |
|
151 $matches = false; |
|
152 if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', str_replace(array("\n", ' '), '', $compiledRoute->getRegex()), $m)) { |
|
153 if ($supportsRedirections && substr($m['url'], -1) === '/') { |
|
154 $conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); |
|
155 $hasTrailingSlash = true; |
|
156 } else { |
|
157 $conditions[] = sprintf("\$pathinfo === %s", var_export(str_replace('\\', '', $m['url']), true)); |
|
158 } |
|
159 } else { |
|
160 if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() != $parentPrefix) { |
|
161 $conditions[] = sprintf("0 === strpos(\$pathinfo, %s)", var_export($compiledRoute->getStaticPrefix(), true)); |
|
162 } |
|
163 |
|
164 $regex = str_replace(array("\n", ' '), '', $compiledRoute->getRegex()); |
|
165 if ($supportsRedirections && $pos = strpos($regex, '/$')) { |
|
166 $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); |
|
167 $hasTrailingSlash = true; |
|
168 } |
|
169 $conditions[] = sprintf("preg_match(%s, \$pathinfo, \$matches)", var_export($regex, true)); |
|
170 |
|
171 $matches = true; |
|
172 } |
|
173 |
|
174 $conditions = implode(' && ', $conditions); |
|
175 |
|
176 $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name); |
|
177 |
|
178 $code[] = <<<EOF |
|
179 // $name |
|
180 if ($conditions) { |
|
181 EOF; |
|
182 |
|
183 if ($req = $route->getRequirement('_method')) { |
|
184 $methods = explode('|', strtoupper($req)); |
|
185 // GET and HEAD are equivalent |
|
186 if (in_array('GET', $methods) && !in_array('HEAD', $methods)) { |
|
187 $methods[] = 'HEAD'; |
|
188 } |
|
189 if (1 === count($methods)) { |
|
190 $code[] = <<<EOF |
|
191 if (\$this->context->getMethod() != '$methods[0]') { |
|
192 \$allow[] = '$methods[0]'; |
|
193 goto $gotoname; |
|
194 } |
|
195 EOF; |
|
196 } else { |
|
197 $methods = implode('\', \'', $methods); |
|
198 $code[] = <<<EOF |
|
199 if (!in_array(\$this->context->getMethod(), array('$methods'))) { |
|
200 \$allow = array_merge(\$allow, array('$methods')); |
|
201 goto $gotoname; |
|
202 } |
|
203 EOF; |
|
204 } |
|
205 } |
|
206 |
|
207 if ($hasTrailingSlash) { |
|
208 $code[] = sprintf(<<<EOF |
|
209 if (substr(\$pathinfo, -1) !== '/') { |
|
210 return \$this->redirect(\$pathinfo.'/', '%s'); |
|
211 } |
|
212 EOF |
|
213 , $name); |
|
214 } |
|
215 |
|
216 if ($scheme = $route->getRequirement('_scheme')) { |
|
217 if (!$supportsRedirections) { |
|
218 throw new \LogicException('The "_scheme" requirement is only supported for route dumper that implements RedirectableUrlMatcherInterface.'); |
|
219 } |
|
220 |
|
221 $code[] = sprintf(<<<EOF |
|
222 if (\$this->context->getScheme() !== '$scheme') { |
|
223 return \$this->redirect(\$pathinfo, '%s', '$scheme'); |
|
224 } |
|
225 EOF |
|
226 , $name); |
|
227 } |
|
228 |
|
229 // optimize parameters array |
|
230 if (true === $matches && $compiledRoute->getDefaults()) { |
|
231 $code[] = sprintf(" return array_merge(\$this->mergeDefaults(\$matches, %s), array('_route' => '%s'));" |
|
232 , str_replace("\n", '', var_export($compiledRoute->getDefaults(), true)), $name); |
|
233 } elseif (true === $matches) { |
|
234 $code[] = sprintf(" \$matches['_route'] = '%s';", $name); |
|
235 $code[] = sprintf(" return \$matches;", $name); |
|
236 } elseif ($compiledRoute->getDefaults()) { |
|
237 $code[] = sprintf(' return %s;', str_replace("\n", '', var_export(array_merge($compiledRoute->getDefaults(), array('_route' => $name)), true))); |
|
238 } else { |
|
239 $code[] = sprintf(" return array('_route' => '%s');", $name); |
|
240 } |
|
241 $code[] = " }"; |
|
242 |
|
243 if ($req) { |
|
244 $code[] = " $gotoname:"; |
|
245 } |
|
246 |
|
247 $code[] = ''; |
|
248 |
|
249 return $code; |
|
250 } |
|
251 |
|
252 private function startClass($class, $baseClass) |
|
253 { |
|
254 return <<<EOF |
|
255 <?php |
|
256 |
|
257 use Symfony\Component\Routing\Exception\MethodNotAllowedException; |
|
258 use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
|
259 use Symfony\Component\Routing\RequestContext; |
|
260 |
|
261 /** |
|
262 * $class |
|
263 * |
|
264 * This class has been auto-generated |
|
265 * by the Symfony Routing Component. |
|
266 */ |
|
267 class $class extends $baseClass |
|
268 { |
|
269 |
|
270 EOF; |
|
271 } |
|
272 |
|
273 private function addConstructor() |
|
274 { |
|
275 return <<<EOF |
|
276 /** |
|
277 * Constructor. |
|
278 */ |
|
279 public function __construct(RequestContext \$context) |
|
280 { |
|
281 \$this->context = \$context; |
|
282 } |
|
283 |
|
284 EOF; |
|
285 } |
|
286 |
|
287 private function endClass() |
|
288 { |
|
289 return <<<EOF |
|
290 } |
|
291 |
|
292 EOF; |
|
293 } |
|
294 } |