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