[ Index ] |
PHP Cross Reference of phpBB-3.2.11-deutsch |
[Summary view] [Print] [Text view]
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\ExpressionLanguage\ExpressionFunctionProviderInterface; 15 use Symfony\Component\ExpressionLanguage\ExpressionLanguage; 16 use Symfony\Component\Routing\Route; 17 use Symfony\Component\Routing\RouteCollection; 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 * This class has been auto-generated 67 * by the Symfony Routing Component. 68 */ 69 class {$options['class']} extends {$options['base_class']} 70 { 71 public function __construct(RequestContext \$context) 72 { 73 \$this->context = \$context; 74 } 75 76 {$this->generateMatchMethod($supportsRedirections)} 77 } 78 79 EOF; 80 } 81 82 public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) 83 { 84 $this->expressionLanguageProviders[] = $provider; 85 } 86 87 /** 88 * Generates the code for the match method implementing UrlMatcherInterface. 89 * 90 * @param bool $supportsRedirections Whether redirections are supported by the base class 91 * 92 * @return string Match method as PHP code 93 */ 94 private function generateMatchMethod($supportsRedirections) 95 { 96 $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); 97 98 return <<<EOF 99 public function match(\$rawPathinfo) 100 { 101 \$allow = array(); 102 \$pathinfo = rawurldecode(\$rawPathinfo); 103 \$context = \$this->context; 104 \$request = \$this->request ?: \$this->createRequest(\$pathinfo); 105 106 $code 107 108 throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); 109 } 110 EOF; 111 } 112 113 /** 114 * Generates PHP code to match a RouteCollection with all its routes. 115 * 116 * @param RouteCollection $routes A RouteCollection instance 117 * @param bool $supportsRedirections Whether redirections are supported by the base class 118 * 119 * @return string PHP code 120 */ 121 private function compileRoutes(RouteCollection $routes, $supportsRedirections) 122 { 123 $fetchedHost = false; 124 125 $groups = $this->groupRoutesByHostRegex($routes); 126 $code = ''; 127 128 foreach ($groups as $collection) { 129 if (null !== $regex = $collection->getAttribute('host_regex')) { 130 if (!$fetchedHost) { 131 $code .= " \$host = \$this->context->getHost();\n\n"; 132 $fetchedHost = true; 133 } 134 135 $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); 136 } 137 138 $tree = $this->buildPrefixTree($collection); 139 $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections); 140 141 if (null !== $regex) { 142 // apply extra indention at each line (except empty ones) 143 $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); 144 $code .= $groupCode; 145 $code .= " }\n\n"; 146 } else { 147 $code .= $groupCode; 148 } 149 } 150 151 return $code; 152 } 153 154 /** 155 * Generates PHP code recursively to match a tree of routes. 156 * 157 * @param DumperPrefixCollection $collection A DumperPrefixCollection instance 158 * @param bool $supportsRedirections Whether redirections are supported by the base class 159 * @param string $parentPrefix Prefix of the parent collection 160 * 161 * @return string PHP code 162 */ 163 private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '') 164 { 165 $code = ''; 166 $prefix = $collection->getPrefix(); 167 $optimizable = 1 < \strlen($prefix) && 1 < \count($collection->all()); 168 $optimizedPrefix = $parentPrefix; 169 170 if ($optimizable) { 171 $optimizedPrefix = $prefix; 172 173 $code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true)); 174 } 175 176 foreach ($collection as $route) { 177 if ($route instanceof DumperCollection) { 178 $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix); 179 } else { 180 $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n"; 181 } 182 } 183 184 if ($optimizable) { 185 $code .= " }\n\n"; 186 // apply extra indention at each line (except empty ones) 187 $code = preg_replace('/^.{2,}$/m', ' $0', $code); 188 } 189 190 return $code; 191 } 192 193 /** 194 * Compiles a single Route to PHP code used to match it against the path info. 195 * 196 * @param Route $route A Route instance 197 * @param string $name The name of the Route 198 * @param bool $supportsRedirections Whether redirections are supported by the base class 199 * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code 200 * 201 * @return string PHP code 202 * 203 * @throws \LogicException 204 */ 205 private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) 206 { 207 $code = ''; 208 $compiledRoute = $route->compile(); 209 $conditions = array(); 210 $hasTrailingSlash = false; 211 $matches = false; 212 $hostMatches = false; 213 $methods = $route->getMethods(); 214 215 // GET and HEAD are equivalent 216 if (\in_array('GET', $methods) && !\in_array('HEAD', $methods)) { 217 $methods[] = 'HEAD'; 218 } 219 220 $supportsTrailingSlash = $supportsRedirections && (!$methods || \in_array('GET', $methods)); 221 222 if (!\count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) { 223 if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) { 224 $conditions[] = sprintf("%s === rtrim(\$pathinfo, '/')", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); 225 $hasTrailingSlash = true; 226 } else { 227 $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true)); 228 } 229 } else { 230 if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { 231 $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true)); 232 } 233 234 $regex = $compiledRoute->getRegex(); 235 if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { 236 $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); 237 $hasTrailingSlash = true; 238 } 239 $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true)); 240 241 $matches = true; 242 } 243 244 if ($compiledRoute->getHostVariables()) { 245 $hostMatches = true; 246 } 247 248 if ($route->getCondition()) { 249 $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); 250 } 251 252 $conditions = implode(' && ', $conditions); 253 254 $code .= <<<EOF 255 // $name 256 if ($conditions) { 257 258 EOF; 259 260 $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name); 261 262 if ($hasTrailingSlash) { 263 $code .= <<<EOF 264 if ('/' === substr(\$pathinfo, -1)) { 265 // no-op 266 } elseif (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) { 267 goto $gotoname; 268 } else { 269 return \$this->redirect(\$rawPathinfo.'/', '$name'); 270 } 271 272 273 EOF; 274 } 275 276 if ($schemes = $route->getSchemes()) { 277 if (!$supportsRedirections) { 278 throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); 279 } 280 $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); 281 if ($methods) { 282 $methods = implode("', '", $methods); 283 $code .= <<<EOF 284 \$requiredSchemes = $schemes; 285 \$hasRequiredScheme = isset(\$requiredSchemes[\$this->context->getScheme()]); 286 if (!in_array(\$this->context->getMethod(), array('$methods'))) { 287 if (\$hasRequiredScheme) { 288 \$allow = array_merge(\$allow, array('$methods')); 289 } 290 goto $gotoname; 291 } 292 if (!\$hasRequiredScheme) { 293 if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) { 294 goto $gotoname; 295 } 296 297 return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)); 298 } 299 300 301 EOF; 302 } else { 303 $code .= <<<EOF 304 \$requiredSchemes = $schemes; 305 if (!isset(\$requiredSchemes[\$this->context->getScheme()])) { 306 if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) { 307 goto $gotoname; 308 } 309 310 return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)); 311 } 312 313 314 EOF; 315 } 316 } elseif ($methods) { 317 if (1 === \count($methods)) { 318 $code .= <<<EOF 319 if (\$this->context->getMethod() != '$methods[0]') { 320 \$allow[] = '$methods[0]'; 321 goto $gotoname; 322 } 323 324 325 EOF; 326 } else { 327 $methods = implode("', '", $methods); 328 $code .= <<<EOF 329 if (!in_array(\$this->context->getMethod(), array('$methods'))) { 330 \$allow = array_merge(\$allow, array('$methods')); 331 goto $gotoname; 332 } 333 334 335 EOF; 336 } 337 } 338 339 // optimize parameters array 340 if ($matches || $hostMatches) { 341 $vars = array(); 342 if ($hostMatches) { 343 $vars[] = '$hostMatches'; 344 } 345 if ($matches) { 346 $vars[] = '$matches'; 347 } 348 $vars[] = "array('_route' => '$name')"; 349 350 $code .= sprintf( 351 " return \$this->mergeDefaults(array_replace(%s), %s);\n", 352 implode(', ', $vars), 353 str_replace("\n", '', var_export($route->getDefaults(), true)) 354 ); 355 } elseif ($route->getDefaults()) { 356 $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); 357 } else { 358 $code .= sprintf(" return array('_route' => '%s');\n", $name); 359 } 360 $code .= " }\n"; 361 362 if ($hasTrailingSlash || $schemes || $methods) { 363 $code .= " $gotoname:\n"; 364 } 365 366 return $code; 367 } 368 369 /** 370 * Groups consecutive routes having the same host regex. 371 * 372 * The result is a collection of collections of routes having the same host regex. 373 * 374 * @param RouteCollection $routes A flat RouteCollection 375 * 376 * @return DumperCollection A collection with routes grouped by host regex in sub-collections 377 */ 378 private function groupRoutesByHostRegex(RouteCollection $routes) 379 { 380 $groups = new DumperCollection(); 381 382 $currentGroup = new DumperCollection(); 383 $currentGroup->setAttribute('host_regex', null); 384 $groups->add($currentGroup); 385 386 foreach ($routes as $name => $route) { 387 $hostRegex = $route->compile()->getHostRegex(); 388 if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { 389 $currentGroup = new DumperCollection(); 390 $currentGroup->setAttribute('host_regex', $hostRegex); 391 $groups->add($currentGroup); 392 } 393 $currentGroup->add(new DumperRoute($name, $route)); 394 } 395 396 return $groups; 397 } 398 399 /** 400 * Organizes the routes into a prefix tree. 401 * 402 * Routes order is preserved such that traversing the tree will traverse the 403 * routes in the origin order. 404 * 405 * @return DumperPrefixCollection 406 */ 407 private function buildPrefixTree(DumperCollection $collection) 408 { 409 $tree = new DumperPrefixCollection(); 410 $current = $tree; 411 412 foreach ($collection as $route) { 413 $current = $current->addPrefixRoute($route); 414 } 415 416 $tree->mergeSlashNodes(); 417 418 return $tree; 419 } 420 421 private function getExpressionLanguage() 422 { 423 if (null === $this->expressionLanguage) { 424 if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { 425 throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); 426 } 427 $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); 428 } 429 430 return $this->expressionLanguage; 431 } 432 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |