RouteCollection.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Routing;
  16. use Cake\Core\App;
  17. use Cake\Error;
  18. use Cake\Routing\Error\MissingRouteException;
  19. use Cake\Routing\Router;
  20. use Cake\Routing\Route\Route;
  21. use Cake\Utility\Inflector;
  22. /**
  23. * Contains a collection of routes.
  24. *
  25. * Provides an interface for adding/removing routes
  26. * and parsing/generating URLs with the routes it contains.
  27. *
  28. * @internal
  29. */
  30. class RouteCollection {
  31. /**
  32. * The routes connected to this collection.
  33. *
  34. * @var array
  35. */
  36. protected $_routeTable = [];
  37. /**
  38. * The routes connected to this collection.
  39. *
  40. * @var array
  41. */
  42. protected $_routes = [];
  43. /**
  44. * The hash map of named routes that are in this collection.
  45. *
  46. * @var array
  47. */
  48. protected $_named = [];
  49. /**
  50. * Add a route to the collection.
  51. *
  52. * @param \Cake\Routing\Route\Route $route The route object to add.
  53. * @param array $options Addtional options for the route. Primarily for the
  54. * `_name` option, which enables named routes.
  55. */
  56. public function add(Route $route, $options = []) {
  57. $this->_routes[] = $route;
  58. // Explicit names
  59. if (isset($options['_name'])) {
  60. $this->_named[$options['_name']] = $route;
  61. }
  62. // Generated names.
  63. $name = $route->getName();
  64. if (!isset($this->_routeTable[$name])) {
  65. $this->_routeTable[$name] = [];
  66. }
  67. $this->_routeTable[$name][] = $route;
  68. // Index path prefixes (for parsing)
  69. $path = $route->staticPath();
  70. if (empty($this->_paths[$path])) {
  71. $this->_paths[$path] = [];
  72. krsort($this->_paths);
  73. }
  74. $this->_paths[$path][] = $route;
  75. }
  76. /**
  77. * Takes the URL string and iterates the routes until one is able to parse the route.
  78. *
  79. * @param string $url URL to parse.
  80. * @return array An array of request parameters parsed from the URL.
  81. * @throws \Cake\Routing\Error\MissingRouteException When a URL has no matching route.
  82. */
  83. public function parse($url) {
  84. foreach (array_keys($this->_paths) as $path) {
  85. if (strpos($url, $path) === 0) {
  86. break;
  87. }
  88. }
  89. $queryParameters = null;
  90. if (strpos($url, '?') !== false) {
  91. list($url, $queryParameters) = explode('?', $url, 2);
  92. parse_str($queryParameters, $queryParameters);
  93. }
  94. foreach ($this->_paths[$path] as $route) {
  95. $r = $route->parse($url);
  96. if ($r === false) {
  97. continue;
  98. }
  99. if ($queryParameters) {
  100. $r['?'] = $queryParameters;
  101. }
  102. return $r;
  103. }
  104. throw new MissingRouteException(['url' => $url]);
  105. }
  106. /**
  107. * Get the set of names from the $url. Accepts both older style array urls,
  108. * and newer style urls containing '_name'
  109. *
  110. * @param array $url The url to match.
  111. * @return string The name of the url
  112. */
  113. protected function _getNames($url) {
  114. $plugin = false;
  115. if (isset($url['plugin']) && $url['plugin'] !== false) {
  116. $plugin = strtolower($url['plugin']);
  117. }
  118. $prefix = false;
  119. if (isset($url['prefix']) && $url['prefix'] !== false) {
  120. $prefix = strtolower($url['prefix']);
  121. }
  122. $controller = strtolower($url['controller']);
  123. $action = strtolower($url['action']);
  124. $names = [
  125. "${controller}:${action}",
  126. "${controller}:_action",
  127. "_controller:${action}",
  128. "_controller:_action"
  129. ];
  130. // No prefix, no plugin
  131. if ($prefix === false && $plugin === false) {
  132. return $names;
  133. }
  134. // Only a plugin
  135. if ($prefix === false) {
  136. return [
  137. "${plugin}.${controller}:${action}",
  138. "${plugin}.${controller}:_action",
  139. "${plugin}._controller:${action}",
  140. "${plugin}._controller:_action",
  141. "_plugin.${controller}:${action}",
  142. "_plugin.${controller}:_action",
  143. "_plugin._controller:${action}",
  144. "_plugin._controller:_action",
  145. ];
  146. }
  147. // Only a prefix
  148. if ($plugin === false) {
  149. return [
  150. "${prefix}:${controller}:${action}",
  151. "${prefix}:${controller}:_action",
  152. "${prefix}:_controller:${action}",
  153. "${prefix}:_controller:_action",
  154. "_prefix:${controller}:${action}",
  155. "_prefix:${controller}:_action",
  156. "_prefix:_controller:${action}",
  157. "_prefix:_controller:_action"
  158. ];
  159. }
  160. // Prefix and plugin has the most options
  161. // as there are 4 factors.
  162. return [
  163. "${prefix}:${plugin}.${controller}:${action}",
  164. "${prefix}:${plugin}.${controller}:_action",
  165. "${prefix}:${plugin}._controller:${action}",
  166. "${prefix}:${plugin}._controller:_action",
  167. "${prefix}:_plugin.${controller}:${action}",
  168. "${prefix}:_plugin.${controller}:_action",
  169. "${prefix}:_plugin._controller:${action}",
  170. "${prefix}:_plugin._controller:_action",
  171. "_prefix:${plugin}.${controller}:${action}",
  172. "_prefix:${plugin}.${controller}:_action",
  173. "_prefix:${plugin}._controller:${action}",
  174. "_prefix:${plugin}._controller:_action",
  175. "_prefix:_plugin.${controller}:${action}",
  176. "_prefix:_plugin.${controller}:_action",
  177. "_prefix:_plugin._controller:${action}",
  178. "_prefix:_plugin._controller:_action",
  179. ];
  180. }
  181. /**
  182. * Reverse route or match a $url array with the defined routes.
  183. * Returns either the string URL generate by the route, or false on failure.
  184. *
  185. * @param array $url The url to match.
  186. * @param array $context The request context to use. Contains _base, _port,
  187. * _host, and _scheme keys.
  188. * @return string|false Either a string on match, or false on failure.
  189. * @throws \Cake\Routing\Error\MissingRouteException when a route cannot be matched.
  190. */
  191. public function match($url, $context) {
  192. // Named routes support optimization.
  193. if (isset($url['_name'])) {
  194. $name = $url['_name'];
  195. unset($url['_name']);
  196. $out = false;
  197. if (isset($this->_named[$name])) {
  198. $route = $this->_named[$name];
  199. $out = $route->match($url + $route->defaults, $context);
  200. }
  201. if ($out) {
  202. return $out;
  203. }
  204. throw new MissingRouteException(['url' => $name]);
  205. }
  206. foreach ($this->_getNames($url) as $name) {
  207. if (empty($this->_routeTable[$name])) {
  208. continue;
  209. }
  210. foreach ($this->_routeTable[$name] as $route) {
  211. $match = $route->match($url, $context);
  212. if ($match) {
  213. return strlen($match) > 1 ? trim($match, '/') : $match;
  214. }
  215. }
  216. }
  217. throw new MissingRouteException(['url' => var_export($url, true)]);
  218. }
  219. /**
  220. * Get all the connected routes as a flat list.
  221. *
  222. * @return array
  223. */
  224. public function routes() {
  225. return $this->_routes;
  226. }
  227. /**
  228. * Get the connected named routes.
  229. *
  230. * @return array
  231. */
  232. public function named() {
  233. return $this->_named;
  234. }
  235. }