PhpMatcherDumper.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing\Matcher\Dumper;
  11. use Symfony\Component\Routing\Route;
  12. /**
  13. * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
  14. *
  15. * @author Fabien Potencier <fabien@symfony.com>
  16. */
  17. class PhpMatcherDumper extends MatcherDumper
  18. {
  19. /**
  20. * Dumps a set of routes to a PHP class.
  21. *
  22. * Available options:
  23. *
  24. * * class: The class name
  25. * * base_class: The base class name
  26. *
  27. * @param array $options An array of options
  28. *
  29. * @return string A PHP class representing the matcher class
  30. */
  31. public function dump(array $options = array())
  32. {
  33. $options = array_merge(array(
  34. 'class' => 'ProjectUrlMatcher',
  35. 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
  36. ), $options);
  37. // trailing slash support is only enabled if we know how to redirect the user
  38. $interfaces = class_implements($options['base_class']);
  39. $supportsRedirections = isset($interfaces['Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface']);
  40. return
  41. $this->startClass($options['class'], $options['base_class']).
  42. $this->addConstructor().
  43. $this->addMatcher($supportsRedirections).
  44. $this->endClass()
  45. ;
  46. }
  47. private function addMatcher($supportsRedirections)
  48. {
  49. $code = array();
  50. foreach ($this->getRoutes()->all() as $name => $route) {
  51. $compiledRoute = $route->compile();
  52. $conditions = array();
  53. $hasTrailingSlash = false;
  54. $matches = false;
  55. if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
  56. if ($supportsRedirections && substr($m['url'], -1) === '/') {
  57. $conditions[] = sprintf("rtrim(\$pathinfo, '/') === '%s'", rtrim(str_replace('\\', '', $m['url']), '/'));
  58. $hasTrailingSlash = true;
  59. } else {
  60. $conditions[] = sprintf("\$pathinfo === '%s'", str_replace('\\', '', $m['url']));
  61. }
  62. } else {
  63. if ($compiledRoute->getStaticPrefix()) {
  64. $conditions[] = sprintf("0 === strpos(\$pathinfo, '%s')", $compiledRoute->getStaticPrefix());
  65. }
  66. $regex = $compiledRoute->getRegex();
  67. if ($supportsRedirections && $pos = strpos($regex, '/$')) {
  68. $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
  69. $hasTrailingSlash = true;
  70. }
  71. $conditions[] = sprintf("preg_match('%s', \$pathinfo, \$matches)", $regex);
  72. $matches = true;
  73. }
  74. $conditions = implode(' && ', $conditions);
  75. $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
  76. $code[] = <<<EOF
  77. // $name
  78. if ($conditions) {
  79. EOF;
  80. if ($req = $route->getRequirement('_method')) {
  81. $methods = array_map('strtolower', explode('|', $req));
  82. if (1 === count($methods)) {
  83. $code[] = <<<EOF
  84. if (\$this->context->getMethod() != '$methods[0]') {
  85. \$allow[] = '$methods[0]';
  86. goto $gotoname;
  87. }
  88. EOF;
  89. } else {
  90. $methods = implode('\', \'', $methods);
  91. $code[] = <<<EOF
  92. if (!in_array(\$this->context->getMethod(), array('$methods'))) {
  93. \$allow = array_merge(\$allow, array('$methods'));
  94. goto $gotoname;
  95. }
  96. EOF;
  97. }
  98. }
  99. if ($hasTrailingSlash) {
  100. $code[] = sprintf(<<<EOF
  101. if (substr(\$pathinfo, -1) !== '/') {
  102. return \$this->redirect(\$pathinfo.'/', '%s');
  103. }
  104. EOF
  105. , $name);
  106. }
  107. if ($scheme = $route->getRequirement('_scheme')) {
  108. if (!$supportsRedirections) {
  109. throw new \LogicException('The "_scheme" requirement is only supported for route dumper that implements RedirectableUrlMatcherInterface.');
  110. }
  111. $code[] = sprintf(<<<EOF
  112. if (\$this->context->getScheme() !== '$scheme') {
  113. return \$this->redirect(\$pathinfo, '%s', '$scheme');
  114. }
  115. EOF
  116. , $name);
  117. }
  118. // optimize parameters array
  119. if (true === $matches && $compiledRoute->getDefaults()) {
  120. $code[] = sprintf(" return array_merge(\$this->mergeDefaults(\$matches, %s), array('_route' => '%s'));"
  121. , str_replace("\n", '', var_export($compiledRoute->getDefaults(), true)), $name);
  122. } elseif (true === $matches) {
  123. $code[] = sprintf(" \$matches['_route'] = '%s';\n return \$matches;", $name);
  124. } elseif ($compiledRoute->getDefaults()) {
  125. $code[] = sprintf(' return %s;', str_replace("\n", '', var_export(array_merge($compiledRoute->getDefaults(), array('_route' => $name)), true)));
  126. } else {
  127. $code[] = sprintf(" return array('_route' => '%s');", $name);
  128. }
  129. $code[] = " }";
  130. if ($req) {
  131. $code[] = " $gotoname:";
  132. }
  133. $code[] = '';
  134. }
  135. $code = implode("\n", $code);
  136. return <<<EOF
  137. public function match(\$pathinfo)
  138. {
  139. \$allow = array();
  140. $code
  141. throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new NotFoundException();
  142. }
  143. EOF;
  144. }
  145. private function startClass($class, $baseClass)
  146. {
  147. return <<<EOF
  148. <?php
  149. use Symfony\Component\Routing\Matcher\Exception\MethodNotAllowedException;
  150. use Symfony\Component\Routing\Matcher\Exception\NotFoundException;
  151. use Symfony\Component\Routing\RequestContext;
  152. /**
  153. * $class
  154. *
  155. * This class has been auto-generated
  156. * by the Symfony Routing Component.
  157. */
  158. class $class extends $baseClass
  159. {
  160. EOF;
  161. }
  162. private function addConstructor()
  163. {
  164. return <<<EOF
  165. /**
  166. * Constructor.
  167. */
  168. public function __construct(RequestContext \$context, array \$defaults = array())
  169. {
  170. \$this->context = \$context;
  171. \$this->defaults = \$defaults;
  172. }
  173. EOF;
  174. }
  175. private function endClass()
  176. {
  177. return <<<EOF
  178. }
  179. EOF;
  180. }
  181. }