PageRenderTime 154ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Symfony/Component/Routing/RouteCompiler.php

https://github.com/sebio/symfony
PHP | 235 lines | 134 code | 32 blank | 69 comment | 23 complexity | ce287d88b5ab9788a3f9cb53e3d8b61b MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.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.potencier@symfony-project.com>
  15. */
  16. class RouteCompiler implements RouteCompilerInterface
  17. {
  18. protected $options;
  19. protected $route;
  20. protected $variables;
  21. protected $firstOptional;
  22. protected $segments;
  23. protected $tokens;
  24. protected $staticPrefix;
  25. protected $regex;
  26. /**
  27. * Compiles the current route instance.
  28. *
  29. * @param Route $route A Route instance
  30. *
  31. * @return CompiledRoute A CompiledRoute instance
  32. */
  33. public function compile(Route $route)
  34. {
  35. $this->route = $route;
  36. $this->firstOptional = 0;
  37. $this->segments = array();
  38. $this->variables = array();
  39. $this->tokens = array();
  40. $this->staticPrefix = '';
  41. $this->regex = '';
  42. $this->options = $this->getOptions();
  43. $this->preCompile();
  44. $this->tokenize();
  45. foreach ($this->tokens as $token) {
  46. call_user_func_array(array($this, 'compileFor'.ucfirst(array_shift($token))), $token);
  47. }
  48. $this->postCompile();
  49. $separator = '';
  50. if (count($this->tokens)) {
  51. $lastToken = $this->tokens[count($this->tokens) - 1];
  52. $separator = 'separator' == $lastToken[0] ? $lastToken[2] : '';
  53. }
  54. $this->regex = "#^".implode("", $this->segments)."".preg_quote($separator, '#')."$#x";
  55. // optimize tokens for generation
  56. $tokens = array();
  57. foreach ($this->tokens as $i => $token) {
  58. if ($i + 1 === count($this->tokens) && 'separator' === $token[0]) {
  59. // trailing /
  60. $tokens[] = array('text', $token[2], '', null);
  61. } elseif ('separator' !== $token[0]) {
  62. $tokens[] = $token;
  63. }
  64. }
  65. $tokens = array_reverse($tokens);
  66. return new CompiledRoute($this->route, $this->staticPrefix, $this->regex, $tokens, $this->variables);
  67. }
  68. /**
  69. * Pre-compiles a route.
  70. */
  71. protected function preCompile()
  72. {
  73. }
  74. /**
  75. * Post-compiles a route.
  76. */
  77. protected function postCompile()
  78. {
  79. // all segments after the last static segment are optional
  80. // be careful, the n-1 is optional only if n is empty
  81. for ($i = $this->firstOptional, $max = count($this->segments); $i < $max; $i++) {
  82. $this->segments[$i] = (0 == $i ? '/?' : '').str_repeat(' ', $i - $this->firstOptional).'(?:'.$this->segments[$i];
  83. $this->segments[] = str_repeat(' ', $max - $i - 1).')?';
  84. }
  85. $this->staticPrefix = '';
  86. foreach ($this->tokens as $token) {
  87. switch ($token[0]) {
  88. case 'separator':
  89. break;
  90. case 'text':
  91. // text is static
  92. $this->staticPrefix .= $token[1].$token[2];
  93. break;
  94. default:
  95. // everything else indicates variable parts. break switch and for loop
  96. break 2;
  97. }
  98. }
  99. }
  100. /**
  101. * Tokenizes the route.
  102. *
  103. * @throws \InvalidArgumentException When route can't be parsed
  104. */
  105. protected function tokenize()
  106. {
  107. $this->tokens = array();
  108. $buffer = $this->route->getPattern();
  109. $afterASeparator = false;
  110. $currentSeparator = '';
  111. // a route is an array of (separator + variable) or (separator + text) segments
  112. while (strlen($buffer)) {
  113. if (false !== $this->tokenizeBufferBefore($buffer, $tokens, $afterASeparator, $currentSeparator)) {
  114. // a custom token
  115. $this->customToken = true;
  116. } else if ($afterASeparator && preg_match('#^\{([\w\d_]+)\}#', $buffer, $match)) {
  117. // a variable
  118. $this->tokens[] = array('variable', $currentSeparator, $match[0], $match[1]);
  119. $currentSeparator = '';
  120. $buffer = substr($buffer, strlen($match[0]));
  121. $afterASeparator = false;
  122. } else if ($afterASeparator && preg_match('#^('.$this->options['text_regex'].')(?:'.$this->options['segment_separators_regex'].'|$)#', $buffer, $match)) {
  123. // a text
  124. $this->tokens[] = array('text', $currentSeparator, $match[1], null);
  125. $currentSeparator = '';
  126. $buffer = substr($buffer, strlen($match[1]));
  127. $afterASeparator = false;
  128. } else if (!$afterASeparator && preg_match('#^'.$this->options['segment_separators_regex'].'#', $buffer, $match)) {
  129. // a separator
  130. $this->tokens[] = array('separator', $currentSeparator, $match[0], null);
  131. $currentSeparator = $match[0];
  132. $buffer = substr($buffer, strlen($match[0]));
  133. $afterASeparator = true;
  134. } else if (false !== $this->tokenizeBufferAfter($buffer, $tokens, $afterASeparator, $currentSeparator)) {
  135. // a custom token
  136. $this->customToken = true;
  137. } else {
  138. // parsing problem
  139. throw new \InvalidArgumentException(sprintf('Unable to parse "%s" route near "%s".', $this->route->getPattern(), $buffer));
  140. }
  141. }
  142. }
  143. /**
  144. * Tokenizes the buffer before default logic is applied.
  145. *
  146. * This method must return false if the buffer has not been parsed.
  147. *
  148. * @param string $buffer The current route buffer
  149. * @param array $tokens An array of current tokens
  150. * @param Boolean $afterASeparator Whether the buffer is just after a separator
  151. * @param string $currentSeparator The last matched separator
  152. *
  153. * @return Boolean true if a token has been generated, false otherwise
  154. */
  155. protected function tokenizeBufferBefore(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
  156. {
  157. return false;
  158. }
  159. /**
  160. * Tokenizes the buffer after default logic is applied.
  161. *
  162. * This method must return false if the buffer has not been parsed.
  163. *
  164. * @param string $buffer The current route buffer
  165. * @param array $tokens An array of current tokens
  166. * @param Boolean $afterASeparator Whether the buffer is just after a separator
  167. * @param string $currentSeparator The last matched separator
  168. *
  169. * @return Boolean true if a token has been generated, false otherwise
  170. */
  171. protected function tokenizeBufferAfter(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
  172. {
  173. return false;
  174. }
  175. protected function compileForText($separator, $text)
  176. {
  177. $this->firstOptional = count($this->segments) + 1;
  178. $this->segments[] = preg_quote($separator, '#').preg_quote($text, '#');
  179. }
  180. protected function compileForVariable($separator, $name, $variable)
  181. {
  182. if (null === $requirement = $this->route->getRequirement($variable)) {
  183. $requirement = $this->options['variable_content_regex'];
  184. }
  185. $this->segments[] = preg_quote($separator, '#').'(?P<'.$variable.'>'.$requirement.')';
  186. $this->variables[$variable] = $name;
  187. if (!$this->route->getDefault($variable)) {
  188. $this->firstOptional = count($this->segments);
  189. }
  190. }
  191. protected function compileForSeparator($separator, $regexSeparator)
  192. {
  193. }
  194. protected function getOptions()
  195. {
  196. $options = $this->route->getOptions();
  197. // compute some regexes
  198. $quoter = function ($a) { return preg_quote($a, '#'); };
  199. $options['segment_separators_regex'] = '(?:'.implode('|', array_map($quoter, $options['segment_separators'])).')';
  200. $options['variable_content_regex'] = '[^'.implode('', array_map($quoter, $options['segment_separators'])).']+?';
  201. return $options;
  202. }
  203. }