router.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <?php namespace Laravel\Routing; use Laravel\Request;
  2. class Delegate {
  3. /**
  4. * The destination of the route delegate.
  5. *
  6. * @var string
  7. */
  8. public $destination;
  9. /**
  10. * Create a new route delegate instance.
  11. *
  12. * @param string $destination
  13. * @return void
  14. */
  15. public function __construct($destination)
  16. {
  17. $this->destination = $destination;
  18. }
  19. }
  20. class Router {
  21. /**
  22. * The route loader instance.
  23. *
  24. * @var Loader
  25. */
  26. public $loader;
  27. /**
  28. * The named routes that have been found so far.
  29. *
  30. * @var array
  31. */
  32. protected $names = array();
  33. /**
  34. * The path the application controllers.
  35. *
  36. * @var string
  37. */
  38. protected $controllers;
  39. /**
  40. * Create a new router for a request method and URI.
  41. *
  42. * @param Loader $loader
  43. * @param string $controllers
  44. * @return void
  45. */
  46. public function __construct(Loader $loader, $controllers)
  47. {
  48. $this->loader = $loader;
  49. $this->controllers = $controllers;
  50. }
  51. /**
  52. * Find a route by name.
  53. *
  54. * The returned array will be identical the array defined in the routes.php file.
  55. *
  56. * @param string $name
  57. * @return array
  58. */
  59. public function find($name)
  60. {
  61. // First we will check the cache of route names. If we have already found the given route,
  62. // we will simply return that route from the cache to improve performance.
  63. if (array_key_exists($name, $this->names)) return $this->names[$name];
  64. // Spin through every route defined for the application searching for a route that has
  65. // a name matching the name passed to the method. If the route is found, it will be
  66. // cached in the array of named routes and returned.
  67. foreach ($this->loader->everything() as $key => $value)
  68. {
  69. if (is_array($value) and isset($value['name']) and $value['name'] === $name)
  70. {
  71. return $this->names[$name] = array($key => $value);
  72. }
  73. }
  74. }
  75. /**
  76. * Search the routes for the route matching a request method and URI.
  77. *
  78. * @param string $method
  79. * @param string $uri
  80. * @return Route
  81. */
  82. public function route($method, $uri)
  83. {
  84. $routes = $this->loader->load($uri);
  85. // Put the request method and URI in route form. Routes begin with
  86. // the request method and a forward slash.
  87. $destination = $method.' /'.trim($uri, '/');
  88. // Check for a literal route match first. If we find one, there is
  89. // no need to spin through all of the routes.
  90. if (isset($routes[$destination]))
  91. {
  92. return Request::$route = new Route($destination, $routes[$destination], array());
  93. }
  94. foreach ($routes as $keys => $callback)
  95. {
  96. // Only check routes that have multiple URIs or wildcards.
  97. // Other routes would have been caught by the check for literal matches.
  98. if (strpos($keys, '(') !== false or strpos($keys, ',') !== false )
  99. {
  100. foreach (explode(', ', $keys) as $key)
  101. {
  102. // Append the provided formats to the route as an optional regular expression.
  103. if ( ! is_null($formats = $this->provides($callback))) $key .= '(\.('.implode('|', $formats).'))?';
  104. if (preg_match('#^'.$this->translate_wildcards($key).'$#', $destination))
  105. {
  106. return Request::$route = new Route($keys, $callback, $this->parameters($destination, $key));
  107. }
  108. }
  109. }
  110. }
  111. return Request::$route = $this->route_to_controller($method, $uri, $destination);
  112. }
  113. /**
  114. * Attempt to find a controller for the incoming request.
  115. *
  116. * @param string $method
  117. * @param string $uri
  118. * @param string $destination
  119. * @return Route
  120. */
  121. protected function route_to_controller($method, $uri, $destination)
  122. {
  123. // If the request is to the root of the application, an ad-hoc route will be generated
  124. // to the home controller's "index" method, making it the default controller method.
  125. if ($uri === '/') return new Route($method.' /', 'home@index');
  126. $segments = explode('/', trim($uri, '/'));
  127. if ( ! is_null($key = $this->controller_key($segments)))
  128. {
  129. // Create the controller name for the current request. This controller
  130. // name will be returned by the anonymous route we will create. Instead
  131. // of using directory slashes, dots will be used to specify the controller
  132. // location with the controllers directory.
  133. $controller = implode('.', array_slice($segments, 0, $key));
  134. // Now that we have the controller path and name, we can slice the controller
  135. // section of the URI from the array of segments.
  136. $segments = array_slice($segments, $key);
  137. // Extract the controller method from the URI segments. If no more segments
  138. // are remaining after slicing off the controller, the "index" method will
  139. // be used as the default controller method.
  140. $method = (count($segments) > 0) ? array_shift($segments) : 'index';
  141. return new Route($destination, $controller.'@'.$method, $segments);
  142. }
  143. }
  144. /**
  145. * Search the controllers for the application and determine if an applicable
  146. * controller exists for the current request.
  147. *
  148. * If a controller is found, the array key for the controller name in the URI
  149. * segments will be returned by the method, otherwise NULL will be returned.
  150. * The deepest possible matching controller will be considered the controller
  151. * that should handle the request.
  152. *
  153. * @param array $segments
  154. * @return int
  155. */
  156. protected function controller_key($segments)
  157. {
  158. foreach (array_reverse($segments, true) as $key => $value)
  159. {
  160. if (file_exists($path = $this->controllers.implode('/', array_slice($segments, 0, $key + 1)).EXT))
  161. {
  162. return $key + 1;
  163. }
  164. }
  165. }
  166. /**
  167. * Get the request formats for which the route provides responses.
  168. *
  169. * @param mixed $callback
  170. * @return array
  171. */
  172. protected function provides($callback)
  173. {
  174. return (is_array($callback) and isset($callback['provides'])) ? explode(', ', $callback['provides']) : null;
  175. }
  176. /**
  177. * Translate route URI wildcards into actual regular expressions.
  178. *
  179. * @param string $key
  180. * @return string
  181. */
  182. protected function translate_wildcards($key)
  183. {
  184. $replacements = 0;
  185. // For optional parameters, first translate the wildcards to their
  186. // regex equivalent, sans the ")?" ending. We will add the endings
  187. // back on after we know how many replacements we made.
  188. $key = str_replace(array('/(:num?)', '/(:any?)'), array('(?:/([0-9]+)', '(?:/([a-zA-Z0-9\.\-_]+)'), $key, $replacements);
  189. $key .= ($replacements > 0) ? str_repeat(')?', $replacements) : '';
  190. return str_replace(array(':num', ':any'), array('[0-9]+', '[a-zA-Z0-9\.\-_]+'), $key);
  191. }
  192. /**
  193. * Extract the parameters from a URI based on a route URI.
  194. *
  195. * Any route segment wrapped in parentheses is considered a parameter.
  196. *
  197. * @param string $uri
  198. * @param string $route
  199. * @return array
  200. */
  201. protected function parameters($uri, $route)
  202. {
  203. return array_values(array_intersect_key(explode('/', $uri), preg_grep('/\(.+\)/', explode('/', $route))));
  204. }
  205. }