RouteCompiler.php 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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;
  11. /**
  12. * RouteCompiler compiles Route instances to CompiledRoute instances.
  13. *
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. */
  16. class RouteCompiler implements RouteCompilerInterface
  17. {
  18. /**
  19. * Compiles the current route instance.
  20. *
  21. * @param Route $route A Route instance
  22. *
  23. * @return CompiledRoute A CompiledRoute instance
  24. */
  25. public function compile(Route $route)
  26. {
  27. $pattern = $route->getPattern();
  28. $len = strlen($pattern);
  29. $tokens = array();
  30. $variables = array();
  31. $pos = 0;
  32. preg_match_all('#.\{([\w\d_]+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
  33. foreach ($matches as $match) {
  34. if ($text = substr($pattern, $pos, $match[0][1] - $pos)) {
  35. $tokens[] = array('text', $text);
  36. }
  37. $seps = array($pattern[$pos]);
  38. $pos = $match[0][1] + strlen($match[0][0]);
  39. $var = $match[1][0];
  40. if ($req = $route->getRequirement($var)) {
  41. $regexp = $req;
  42. } else {
  43. if ($pos !== $len) {
  44. $seps[] = $pattern[$pos];
  45. }
  46. $regexp = sprintf('[^%s]+?', preg_quote(implode('', array_unique($seps)), '#'));
  47. }
  48. $tokens[] = array('variable', $match[0][0][0], $regexp, $var);
  49. if (in_array($var, $variables)) {
  50. throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $route->getPattern(), $var));
  51. }
  52. $variables[] = $var;
  53. }
  54. if ($pos < $len) {
  55. $tokens[] = array('text', substr($pattern, $pos));
  56. }
  57. // find the first optional token
  58. $firstOptional = INF;
  59. for ($i = count($tokens) - 1; $i >= 0; $i--) {
  60. $token = $tokens[$i];
  61. if ('variable' === $token[0] && $route->hasDefault($token[3])) {
  62. $firstOptional = $i;
  63. } else {
  64. break;
  65. }
  66. }
  67. // compute the matching regexp
  68. $regexp = '';
  69. for ($i = 0, $nbToken = count($tokens); $i < $nbToken; $i++) {
  70. $regexp .= $this->computeRegexp($tokens, $i, $firstOptional);
  71. }
  72. return new CompiledRoute(
  73. $route,
  74. 'text' === $tokens[0][0] ? $tokens[0][1] : '',
  75. sprintf("#^%s$#s", $regexp),
  76. array_reverse($tokens),
  77. $variables
  78. );
  79. }
  80. /**
  81. * Computes the regexp used to match the token.
  82. *
  83. * @param array $tokens The route tokens
  84. * @param integer $index The index of the current token
  85. * @param integer $firstOptional The index of the first optional token
  86. *
  87. * @return string The regexp
  88. */
  89. private function computeRegexp(array $tokens, $index, $firstOptional)
  90. {
  91. $token = $tokens[$index];
  92. if('text' === $token[0]) {
  93. // Text tokens
  94. return preg_quote($token[1], '#');
  95. } else {
  96. // Variable tokens
  97. if (0 === $index && 0 === $firstOptional && 1 == count($tokens)) {
  98. // When the only token is an optional variable token, the separator is required
  99. return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], '#'), $token[3], $token[2]);
  100. } else {
  101. $nbTokens = count($tokens);
  102. $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], '#'), $token[3], $token[2]);
  103. if ($index >= $firstOptional) {
  104. // Enclose each optional tokens in a subpattern to make it optional
  105. $regexp = "(?:$regexp";
  106. if ($nbTokens - 1 == $index) {
  107. // Close the optional subpatterns
  108. $regexp .= str_repeat(")?", $nbTokens - $firstOptional);
  109. }
  110. }
  111. return $regexp;
  112. }
  113. }
  114. }
  115. }