diff options
Diffstat (limited to 'vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php')
-rw-r--r-- | vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php new file mode 100644 index 00000000..dc17ffbe --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php | |||
@@ -0,0 +1,378 @@ | |||
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 | * @author Tobias Schultze <http://tobion.de> | ||
22 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
23 | */ | ||
24 | class PhpMatcherDumper extends MatcherDumper | ||
25 | { | ||
26 | /** | ||
27 | * Dumps a set of routes to a PHP class. | ||
28 | * | ||
29 | * Available options: | ||
30 | * | ||
31 | * * class: The class name | ||
32 | * * base_class: The base class name | ||
33 | * | ||
34 | * @param array $options An array of options | ||
35 | * | ||
36 | * @return string A PHP class representing the matcher class | ||
37 | */ | ||
38 | public function dump(array $options = array()) | ||
39 | { | ||
40 | $options = array_replace(array( | ||
41 | 'class' => 'ProjectUrlMatcher', | ||
42 | 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', | ||
43 | ), $options); | ||
44 | |||
45 | // trailing slash support is only enabled if we know how to redirect the user | ||
46 | $interfaces = class_implements($options['base_class']); | ||
47 | $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); | ||
48 | |||
49 | return <<<EOF | ||
50 | <?php | ||
51 | |||
52 | use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||
53 | use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||
54 | use Symfony\Component\Routing\RequestContext; | ||
55 | |||
56 | /** | ||
57 | * {$options['class']} | ||
58 | * | ||
59 | * This class has been auto-generated | ||
60 | * by the Symfony Routing Component. | ||
61 | */ | ||
62 | class {$options['class']} extends {$options['base_class']} | ||
63 | { | ||
64 | /** | ||
65 | * Constructor. | ||
66 | */ | ||
67 | public function __construct(RequestContext \$context) | ||
68 | { | ||
69 | \$this->context = \$context; | ||
70 | } | ||
71 | |||
72 | {$this->generateMatchMethod($supportsRedirections)} | ||
73 | } | ||
74 | |||
75 | EOF; | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * Generates the code for the match method implementing UrlMatcherInterface. | ||
80 | * | ||
81 | * @param Boolean $supportsRedirections Whether redirections are supported by the base class | ||
82 | * | ||
83 | * @return string Match method as PHP code | ||
84 | */ | ||
85 | private function generateMatchMethod($supportsRedirections) | ||
86 | { | ||
87 | $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); | ||
88 | |||
89 | return <<<EOF | ||
90 | public function match(\$pathinfo) | ||
91 | { | ||
92 | \$allow = array(); | ||
93 | \$pathinfo = rawurldecode(\$pathinfo); | ||
94 | |||
95 | $code | ||
96 | |||
97 | throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); | ||
98 | } | ||
99 | EOF; | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * Generates PHP code to match a RouteCollection with all its routes. | ||
104 | * | ||
105 | * @param RouteCollection $routes A RouteCollection instance | ||
106 | * @param Boolean $supportsRedirections Whether redirections are supported by the base class | ||
107 | * | ||
108 | * @return string PHP code | ||
109 | */ | ||
110 | private function compileRoutes(RouteCollection $routes, $supportsRedirections) | ||
111 | { | ||
112 | $fetchedHost = false; | ||
113 | |||
114 | $groups = $this->groupRoutesByHostRegex($routes); | ||
115 | $code = ''; | ||
116 | |||
117 | foreach ($groups as $collection) { | ||
118 | if (null !== $regex = $collection->getAttribute('host_regex')) { | ||
119 | if (!$fetchedHost) { | ||
120 | $code .= " \$host = \$this->context->getHost();\n\n"; | ||
121 | $fetchedHost = true; | ||
122 | } | ||
123 | |||
124 | $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); | ||
125 | } | ||
126 | |||
127 | $tree = $this->buildPrefixTree($collection); | ||
128 | $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections); | ||
129 | |||
130 | if (null !== $regex) { | ||
131 | // apply extra indention at each line (except empty ones) | ||
132 | $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); | ||
133 | $code .= $groupCode; | ||
134 | $code .= " }\n\n"; | ||
135 | } else { | ||
136 | $code .= $groupCode; | ||
137 | } | ||
138 | } | ||
139 | |||
140 | return $code; | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Generates PHP code recursively to match a tree of routes | ||
145 | * | ||
146 | * @param DumperPrefixCollection $collection A DumperPrefixCollection instance | ||
147 | * @param Boolean $supportsRedirections Whether redirections are supported by the base class | ||
148 | * @param string $parentPrefix Prefix of the parent collection | ||
149 | * | ||
150 | * @return string PHP code | ||
151 | */ | ||
152 | private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '') | ||
153 | { | ||
154 | $code = ''; | ||
155 | $prefix = $collection->getPrefix(); | ||
156 | $optimizable = 1 < strlen($prefix) && 1 < count($collection->all()); | ||
157 | $optimizedPrefix = $parentPrefix; | ||
158 | |||
159 | if ($optimizable) { | ||
160 | $optimizedPrefix = $prefix; | ||
161 | |||
162 | $code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true)); | ||
163 | } | ||
164 | |||
165 | foreach ($collection as $route) { | ||
166 | if ($route instanceof DumperCollection) { | ||
167 | $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix); | ||
168 | } else { | ||
169 | $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n"; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | if ($optimizable) { | ||
174 | $code .= " }\n\n"; | ||
175 | // apply extra indention at each line (except empty ones) | ||
176 | $code = preg_replace('/^.{2,}$/m', ' $0', $code); | ||
177 | } | ||
178 | |||
179 | return $code; | ||
180 | } | ||
181 | |||
182 | /** | ||
183 | * Compiles a single Route to PHP code used to match it against the path info. | ||
184 | * | ||
185 | * @param Route $route A Route instance | ||
186 | * @param string $name The name of the Route | ||
187 | * @param Boolean $supportsRedirections Whether redirections are supported by the base class | ||
188 | * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code | ||
189 | * | ||
190 | * @return string PHP code | ||
191 | * | ||
192 | * @throws \LogicException | ||
193 | */ | ||
194 | private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) | ||
195 | { | ||
196 | $code = ''; | ||
197 | $compiledRoute = $route->compile(); | ||
198 | $conditions = array(); | ||
199 | $hasTrailingSlash = false; | ||
200 | $matches = false; | ||
201 | $hostMatches = false; | ||
202 | $methods = array(); | ||
203 | |||
204 | if ($req = $route->getRequirement('_method')) { | ||
205 | $methods = explode('|', strtoupper($req)); | ||
206 | // GET and HEAD are equivalent | ||
207 | if (in_array('GET', $methods) && !in_array('HEAD', $methods)) { | ||
208 | $methods[] = 'HEAD'; | ||
209 | } | ||
210 | } | ||
211 | |||
212 | $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods)); | ||
213 | |||
214 | if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) { | ||
215 | if ($supportsTrailingSlash && substr($m['url'], -1) === '/') { | ||
216 | $conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); | ||
217 | $hasTrailingSlash = true; | ||
218 | } else { | ||
219 | $conditions[] = sprintf("\$pathinfo === %s", var_export(str_replace('\\', '', $m['url']), true)); | ||
220 | } | ||
221 | } else { | ||
222 | if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { | ||
223 | $conditions[] = sprintf("0 === strpos(\$pathinfo, %s)", var_export($compiledRoute->getStaticPrefix(), true)); | ||
224 | } | ||
225 | |||
226 | $regex = $compiledRoute->getRegex(); | ||
227 | if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { | ||
228 | $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); | ||
229 | $hasTrailingSlash = true; | ||
230 | } | ||
231 | $conditions[] = sprintf("preg_match(%s, \$pathinfo, \$matches)", var_export($regex, true)); | ||
232 | |||
233 | $matches = true; | ||
234 | } | ||
235 | |||
236 | if ($compiledRoute->getHostVariables()) { | ||
237 | $hostMatches = true; | ||
238 | } | ||
239 | |||
240 | $conditions = implode(' && ', $conditions); | ||
241 | |||
242 | $code .= <<<EOF | ||
243 | // $name | ||
244 | if ($conditions) { | ||
245 | |||
246 | EOF; | ||
247 | |||
248 | if ($methods) { | ||
249 | $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name); | ||
250 | |||
251 | if (1 === count($methods)) { | ||
252 | $code .= <<<EOF | ||
253 | if (\$this->context->getMethod() != '$methods[0]') { | ||
254 | \$allow[] = '$methods[0]'; | ||
255 | goto $gotoname; | ||
256 | } | ||
257 | |||
258 | |||
259 | EOF; | ||
260 | } else { | ||
261 | $methods = implode("', '", $methods); | ||
262 | $code .= <<<EOF | ||
263 | if (!in_array(\$this->context->getMethod(), array('$methods'))) { | ||
264 | \$allow = array_merge(\$allow, array('$methods')); | ||
265 | goto $gotoname; | ||
266 | } | ||
267 | |||
268 | |||
269 | EOF; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | if ($hasTrailingSlash) { | ||
274 | $code .= <<<EOF | ||
275 | if (substr(\$pathinfo, -1) !== '/') { | ||
276 | return \$this->redirect(\$pathinfo.'/', '$name'); | ||
277 | } | ||
278 | |||
279 | |||
280 | EOF; | ||
281 | } | ||
282 | |||
283 | if ($scheme = $route->getRequirement('_scheme')) { | ||
284 | if (!$supportsRedirections) { | ||
285 | throw new \LogicException('The "_scheme" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); | ||
286 | } | ||
287 | |||
288 | $code .= <<<EOF | ||
289 | if (\$this->context->getScheme() !== '$scheme') { | ||
290 | return \$this->redirect(\$pathinfo, '$name', '$scheme'); | ||
291 | } | ||
292 | |||
293 | |||
294 | EOF; | ||
295 | } | ||
296 | |||
297 | // optimize parameters array | ||
298 | if ($matches || $hostMatches) { | ||
299 | $vars = array(); | ||
300 | if ($hostMatches) { | ||
301 | $vars[] = '$hostMatches'; | ||
302 | } | ||
303 | if ($matches) { | ||
304 | $vars[] = '$matches'; | ||
305 | } | ||
306 | $vars[] = "array('_route' => '$name')"; | ||
307 | |||
308 | $code .= sprintf(" return \$this->mergeDefaults(array_replace(%s), %s);\n" | ||
309 | , implode(', ', $vars), str_replace("\n", '', var_export($route->getDefaults(), true))); | ||
310 | |||
311 | } elseif ($route->getDefaults()) { | ||
312 | $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); | ||
313 | } else { | ||
314 | $code .= sprintf(" return array('_route' => '%s');\n", $name); | ||
315 | } | ||
316 | $code .= " }\n"; | ||
317 | |||
318 | if ($methods) { | ||
319 | $code .= " $gotoname:\n"; | ||
320 | } | ||
321 | |||
322 | return $code; | ||
323 | } | ||
324 | |||
325 | /** | ||
326 | * Groups consecutive routes having the same host regex. | ||
327 | * | ||
328 | * The result is a collection of collections of routes having the same host regex. | ||
329 | * | ||
330 | * @param RouteCollection $routes A flat RouteCollection | ||
331 | * | ||
332 | * @return DumperCollection A collection with routes grouped by host regex in sub-collections | ||
333 | */ | ||
334 | private function groupRoutesByHostRegex(RouteCollection $routes) | ||
335 | { | ||
336 | $groups = new DumperCollection(); | ||
337 | |||
338 | $currentGroup = new DumperCollection(); | ||
339 | $currentGroup->setAttribute('host_regex', null); | ||
340 | $groups->add($currentGroup); | ||
341 | |||
342 | foreach ($routes as $name => $route) { | ||
343 | $hostRegex = $route->compile()->getHostRegex(); | ||
344 | if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { | ||
345 | $currentGroup = new DumperCollection(); | ||
346 | $currentGroup->setAttribute('host_regex', $hostRegex); | ||
347 | $groups->add($currentGroup); | ||
348 | } | ||
349 | $currentGroup->add(new DumperRoute($name, $route)); | ||
350 | } | ||
351 | |||
352 | return $groups; | ||
353 | } | ||
354 | |||
355 | /** | ||
356 | * Organizes the routes into a prefix tree. | ||
357 | * | ||
358 | * Routes order is preserved such that traversing the tree will traverse the | ||
359 | * routes in the origin order. | ||
360 | * | ||
361 | * @param DumperCollection $collection A collection of routes | ||
362 | * | ||
363 | * @return DumperPrefixCollection | ||
364 | */ | ||
365 | private function buildPrefixTree(DumperCollection $collection) | ||
366 | { | ||
367 | $tree = new DumperPrefixCollection(); | ||
368 | $current = $tree; | ||
369 | |||
370 | foreach ($collection as $route) { | ||
371 | $current = $current->addPrefixRoute($route); | ||
372 | } | ||
373 | |||
374 | $tree->mergeSlashNodes(); | ||
375 | |||
376 | return $tree; | ||
377 | } | ||
378 | } | ||