RouteCollection.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. * @link http://cakephp.org CakePHP(tm) Project
  11. * @since 3.0.0
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\Routing;
  15. use Cake\Network\Request;
  16. use Cake\Routing\Route\Route;
  17. /**
  18. * RouteCollection is used to operate on a set of routes.
  19. * It stores routes both in a linear list in order of connection, as well
  20. * as storing them in a hash-table indexed by a routes' name.
  21. *
  22. */
  23. class RouteCollection implements \Countable {
  24. /**
  25. * A hash table of routes indexed by route names.
  26. * Used for reverse routing.
  27. *
  28. * @var array
  29. */
  30. protected $_routeTable = [];
  31. /**
  32. * A list of routes connected, in the order they were connected.
  33. * Used for parsing incoming urls.
  34. *
  35. * @var array
  36. */
  37. protected $_routes = [];
  38. /**
  39. * The top most request's context. Updated whenever
  40. * requests are pushed/popped off the stack in Router.
  41. *
  42. * @var array
  43. */
  44. protected $_requestContext = [
  45. '_base' => '',
  46. '_port' => 80,
  47. '_scheme' => 'http',
  48. '_host' => 'localhost',
  49. ];
  50. /**
  51. * Add a route to the collection.
  52. *
  53. * Appends the route to the list of routes, and the route hashtable.
  54. *
  55. * @param \Cake\Routing\Route\Route $route The route to add
  56. * @return void
  57. */
  58. public function add(Route $route) {
  59. $name = $route->getName();
  60. if (!isset($this->_routeTable[$name])) {
  61. $this->_routeTable[$name] = [];
  62. }
  63. $this->_routeTable[$name][] = $route;
  64. $this->_routes[] = $route;
  65. }
  66. /**
  67. * Reverse route or match a $url array with the defined routes.
  68. * Returns either the string URL generate by the route, or false on failure.
  69. *
  70. * @param array $url The url to match.
  71. * @return void
  72. */
  73. public function match($url) {
  74. $names = $this->_getNames($url);
  75. unset($url['_name']);
  76. foreach ($names as $name) {
  77. if (isset($this->_routeTable[$name])) {
  78. $output = $this->_matchRoutes($this->_routeTable[$name], $url);
  79. if ($output) {
  80. return $output;
  81. }
  82. }
  83. }
  84. return $this->_matchRoutes($this->_routes, $url);
  85. }
  86. /**
  87. * Matches a set of routes with a given $url and $params
  88. *
  89. * @param array $routes An array of routes to match against.
  90. * @param array $url The url to match.
  91. * @return mixed Either false on failure, or a string on success.
  92. */
  93. protected function _matchRoutes($routes, $url) {
  94. for ($i = 0, $len = count($routes); $i < $len; $i++) {
  95. $match = $routes[$i]->match($url, $this->_requestContext);
  96. if ($match) {
  97. return strlen($match) > 1 ? trim($match, '/') : $match;
  98. }
  99. }
  100. return false;
  101. }
  102. /**
  103. * Get the set of names from the $url. Accepts both older style array urls,
  104. * and newer style urls containing '_name'
  105. *
  106. * @param array $url The url to match.
  107. * @return string The name of the url
  108. */
  109. protected function _getNames($url) {
  110. $name = false;
  111. if (isset($url['_name'])) {
  112. $name = $url['_name'];
  113. }
  114. $plugin = false;
  115. if (isset($url['plugin'])) {
  116. $plugin = $url['plugin'];
  117. }
  118. $fallbacks = [
  119. '%2$s:%3$s',
  120. '%2$s:_action',
  121. '_controller:%3$s',
  122. '_controller:_action'
  123. ];
  124. if ($plugin) {
  125. $fallbacks = [
  126. '%1$s.%2$s:%3$s',
  127. '%1$s.%2$s:_action',
  128. '%1$s._controller:%3$s',
  129. '%1$s._controller:_action',
  130. '_plugin._controller:%3$s',
  131. '_plugin._controller:_action',
  132. '_controller:_action'
  133. ];
  134. }
  135. foreach ($fallbacks as $i => $template) {
  136. $fallbacks[$i] = strtolower(sprintf($template, $plugin, $url['controller'], $url['action']));
  137. }
  138. if ($name) {
  139. array_unshift($fallbacks, $name);
  140. }
  141. return $fallbacks;
  142. }
  143. /**
  144. * Takes the URL string and iterates the routes until one is able to parse the route.
  145. *
  146. * @param string $url Url to parse.
  147. * @return array An array of request parameters parsed from the url.
  148. */
  149. public function parse($url) {
  150. $queryParameters = null;
  151. if (strpos($url, '?') !== false) {
  152. list($url, $queryParameters) = explode('?', $url, 2);
  153. parse_str($queryParameters, $queryParameters);
  154. }
  155. $out = array();
  156. for ($i = 0, $len = count($this); $i < $len; $i++) {
  157. $r = $this->_routes[$i]->parse($url);
  158. if ($r !== false && $queryParameters) {
  159. $r['?'] = $queryParameters;
  160. return $r;
  161. }
  162. if ($r !== false) {
  163. return $r;
  164. }
  165. }
  166. return $out;
  167. }
  168. /**
  169. * Promote a route (by default, the last one added) to the beginning of the list.
  170. * Also promotes the route to the head of its named slice in the named route
  171. * table.
  172. *
  173. * @param int $which A zero-based array index representing
  174. * the route to move. For example,
  175. * if 3 routes have been added, the last route would be 2.
  176. * @return bool Returns false if no route exists at the position
  177. * specified by $which.
  178. */
  179. public function promote($which) {
  180. if ($which === null) {
  181. $which = count($this->_routes) - 1;
  182. }
  183. if (!isset($this->_routes[$which])) {
  184. return false;
  185. }
  186. $route =& $this->_routes[$which];
  187. unset($this->_routes[$which]);
  188. array_unshift($this->_routes, $route);
  189. $name = $route->getName();
  190. $routes = $this->_routeTable[$name];
  191. $index = array_search($route, $routes, true);
  192. unset($this->_routeTable[$name][$index]);
  193. array_unshift($this->_routeTable[$name], $route);
  194. return true;
  195. }
  196. /**
  197. * Get route(s) out of the collection.
  198. *
  199. * If a string argument is provided, the first matching
  200. * route for the provided name will be returned.
  201. *
  202. * If an integer argument is provided, the route
  203. * with that index will be returned.
  204. *
  205. * @param mixed $index The index or name of the route you want.
  206. * @return mixed Either the route object or null.
  207. */
  208. public function get($index) {
  209. if (is_string($index)) {
  210. $routes = isset($this->_routeTable[$index]) ? $this->_routeTable[$index] : [null];
  211. return $routes[0];
  212. }
  213. return isset($this->_routes[$index]) ? $this->_routes[$index] : null;
  214. }
  215. /**
  216. * Get the list of all connected routes.
  217. *
  218. * @return array.
  219. */
  220. public function all() {
  221. return $this->_routes;
  222. }
  223. /**
  224. * Part of the countable interface.
  225. *
  226. * @return int The number of connected routes.
  227. */
  228. public function count() {
  229. return count($this->_routes);
  230. }
  231. /**
  232. * Populate the request context used to generate URL's
  233. * Generally set to the last/most recent request.
  234. *
  235. * @param \Cake\Network\Request $request Request instance.
  236. * @return void
  237. */
  238. public function setContext(Request $request) {
  239. $this->_requestContext = [
  240. '_base' => $request->base,
  241. '_port' => $request->port(),
  242. '_scheme' => $request->scheme(),
  243. '_host' => $request->host()
  244. ];
  245. }
  246. /**
  247. * Sets which extensions routes will use.
  248. *
  249. * @param array $extensions The extensions for routes to use.
  250. * @return void
  251. */
  252. public function parseExtensions(array $extensions) {
  253. foreach ($this->_routes as $route) {
  254. $route->parseExtensions($extensions);
  255. }
  256. }
  257. }