PageRenderTime 33ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/laravel/routing/router.php

https://bitbucket.org/Maron1/taqman
PHP | 599 lines | 264 code | 74 blank | 261 comment | 27 complexity | 18732d6ba9c1d98c00cffa306f678caf MD5 | raw file
  1. <?php namespace Laravel\Routing;
  2. use Closure;
  3. use Laravel\Str;
  4. use Laravel\Bundle;
  5. use Laravel\Request;
  6. class Router {
  7. /**
  8. * The route names that have been matched.
  9. *
  10. * @var array
  11. */
  12. public static $names = array();
  13. /**
  14. * The actions that have been reverse routed.
  15. *
  16. * @var array
  17. */
  18. public static $uses = array();
  19. /**
  20. * All of the routes that have been registered.
  21. *
  22. * @var array
  23. */
  24. public static $routes = array(
  25. 'GET' => array(),
  26. 'POST' => array(),
  27. 'PUT' => array(),
  28. 'DELETE' => array(),
  29. 'PATCH' => array(),
  30. 'HEAD' => array(),
  31. 'OPTIONS'=> array(),
  32. );
  33. /**
  34. * All of the "fallback" routes that have been registered.
  35. *
  36. * @var array
  37. */
  38. public static $fallback = array(
  39. 'GET' => array(),
  40. 'POST' => array(),
  41. 'PUT' => array(),
  42. 'DELETE' => array(),
  43. 'PATCH' => array(),
  44. 'HEAD' => array(),
  45. 'OPTIONS'=> array(),
  46. );
  47. /**
  48. * The current attributes being shared by routes.
  49. */
  50. public static $group;
  51. /**
  52. * The "handles" clause for the bundle currently being routed.
  53. *
  54. * @var string
  55. */
  56. public static $bundle;
  57. /**
  58. * The number of URI segments allowed as method arguments.
  59. *
  60. * @var int
  61. */
  62. public static $segments = 5;
  63. /**
  64. * The wildcard patterns supported by the router.
  65. *
  66. * @var array
  67. */
  68. public static $patterns = array(
  69. '(:num)' => '([0-9]+)',
  70. '(:any)' => '([a-zA-Z0-9\.\-_%=]+)',
  71. '(:segment)' => '([^/]+)',
  72. '(:all)' => '(.*)',
  73. );
  74. /**
  75. * The optional wildcard patterns supported by the router.
  76. *
  77. * @var array
  78. */
  79. public static $optional = array(
  80. '/(:num?)' => '(?:/([0-9]+)',
  81. '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_%=]+)',
  82. '/(:segment?)' => '(?:/([^/]+)',
  83. '/(:all?)' => '(?:/(.*)',
  84. );
  85. /**
  86. * An array of HTTP request methods.
  87. *
  88. * @var array
  89. */
  90. public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS');
  91. /**
  92. * Register a HTTPS route with the router.
  93. *
  94. * @param string $method
  95. * @param string|array $route
  96. * @param mixed $action
  97. * @return void
  98. */
  99. public static function secure($method, $route, $action)
  100. {
  101. $action = static::action($action);
  102. $action['https'] = true;
  103. static::register($method, $route, $action);
  104. }
  105. /**
  106. * Register many request URIs to a single action.
  107. *
  108. * <code>
  109. * // Register a group of URIs for an action
  110. * Router::share(array(array('GET', '/'), array('POST', '/')), 'home@index');
  111. * </code>
  112. *
  113. * @param array $routes
  114. * @param mixed $action
  115. * @return void
  116. */
  117. public static function share($routes, $action)
  118. {
  119. foreach ($routes as $route)
  120. {
  121. static::register($route[0], $route[1], $action);
  122. }
  123. }
  124. /**
  125. * Register a group of routes that share attributes.
  126. *
  127. * @param array $attributes
  128. * @param Closure $callback
  129. * @return void
  130. */
  131. public static function group($attributes, Closure $callback)
  132. {
  133. // Route groups allow the developer to specify attributes for a group
  134. // of routes. To register them, we'll set a static property on the
  135. // router so that the register method will see them.
  136. static::$group = $attributes;
  137. call_user_func($callback);
  138. // Once the routes have been registered, we want to set the group to
  139. // null so the attributes will not be given to any of the routes
  140. // that are added after the group is declared.
  141. static::$group = null;
  142. }
  143. /**
  144. * Register a route with the router.
  145. *
  146. * <code>
  147. * // Register a route with the router
  148. * Router::register('GET', '/', function() {return 'Home!';});
  149. *
  150. * // Register a route that handles multiple URIs with the router
  151. * Router::register(array('GET', '/', 'GET /home'), function() {return 'Home!';});
  152. * </code>
  153. *
  154. * @param string $method
  155. * @param string|array $route
  156. * @param mixed $action
  157. * @return void
  158. */
  159. public static function register($method, $route, $action)
  160. {
  161. if (ctype_digit($route)) $route = "({$route})";
  162. if (is_string($route)) $route = explode(', ', $route);
  163. // If the developer is registering multiple request methods to handle
  164. // the URI, we'll spin through each method and register the route
  165. // for each of them along with each URI and action.
  166. if (is_array($method))
  167. {
  168. foreach ($method as $http)
  169. {
  170. static::register($http, $route, $action);
  171. }
  172. return;
  173. }
  174. foreach ((array) $route as $uri)
  175. {
  176. // If the URI begins with a splat, we'll call the universal method, which
  177. // will register a route for each of the request methods supported by
  178. // the router. This is just a notational short-cut.
  179. if ($method == '*')
  180. {
  181. foreach (static::$methods as $method)
  182. {
  183. static::register($method, $route, $action);
  184. }
  185. continue;
  186. }
  187. $uri = ltrim(str_replace('(:bundle)', static::$bundle, $uri), '/');
  188. if($uri == '')
  189. {
  190. $uri = '/';
  191. }
  192. // If the URI begins with a wildcard, we want to add this route to the
  193. // array of "fallback" routes. Fallback routes are always processed
  194. // last when parsing routes since they are very generic and could
  195. // overload bundle routes that are registered.
  196. if ($uri[0] == '(')
  197. {
  198. $routes =& static::$fallback;
  199. }
  200. else
  201. {
  202. $routes =& static::$routes;
  203. }
  204. // If the action is an array, we can simply add it to the array of
  205. // routes keyed by the URI. Otherwise, we will need to call into
  206. // the action method to get a valid action array.
  207. if (is_array($action))
  208. {
  209. $routes[$method][$uri] = $action;
  210. }
  211. else
  212. {
  213. $routes[$method][$uri] = static::action($action);
  214. }
  215. // If a group is being registered, we'll merge all of the group
  216. // options into the action, giving preference to the action
  217. // for options that are specified in both.
  218. if ( ! is_null(static::$group))
  219. {
  220. $routes[$method][$uri] += static::$group;
  221. }
  222. // If the HTTPS option is not set on the action, we'll use the
  223. // value given to the method. The secure method passes in the
  224. // HTTPS value in as a parameter short-cut.
  225. if ( ! isset($routes[$method][$uri]['https']))
  226. {
  227. $routes[$method][$uri]['https'] = false;
  228. }
  229. }
  230. }
  231. /**
  232. * Convert a route action to a valid action array.
  233. *
  234. * @param mixed $action
  235. * @return array
  236. */
  237. protected static function action($action)
  238. {
  239. // If the action is a string, it is a pointer to a controller, so we
  240. // need to add it to the action array as a "uses" clause, which will
  241. // indicate to the route to call the controller.
  242. if (is_string($action))
  243. {
  244. $action = array('uses' => $action);
  245. }
  246. // If the action is a Closure, we will manually put it in an array
  247. // to work around a bug in PHP 5.3.2 which causes Closures cast
  248. // as arrays to become null. We'll remove this.
  249. elseif ($action instanceof Closure)
  250. {
  251. $action = array($action);
  252. }
  253. return (array) $action;
  254. }
  255. /**
  256. * Register a secure controller with the router.
  257. *
  258. * @param string|array $controllers
  259. * @param string|array $defaults
  260. * @return void
  261. */
  262. public static function secure_controller($controllers, $defaults = 'index')
  263. {
  264. static::controller($controllers, $defaults, true);
  265. }
  266. /**
  267. * Register a controller with the router.
  268. *
  269. * @param string|array $controllers
  270. * @param string|array $defaults
  271. * @param bool $https
  272. * @return void
  273. */
  274. public static function controller($controllers, $defaults = 'index', $https = null)
  275. {
  276. foreach ((array) $controllers as $identifier)
  277. {
  278. list($bundle, $controller) = Bundle::parse($identifier);
  279. // First we need to replace the dots with slashes in the controller name
  280. // so that it is in directory format. The dots allow the developer to use
  281. // a cleaner syntax when specifying the controller. We will also grab the
  282. // root URI for the controller's bundle.
  283. $controller = str_replace('.', '/', $controller);
  284. $root = Bundle::option($bundle, 'handles');
  285. // If the controller is a "home" controller, we'll need to also build an
  286. // index method route for the controller. We'll remove "home" from the
  287. // route root and setup a route to point to the index method.
  288. if (ends_with($controller, 'home'))
  289. {
  290. static::root($identifier, $controller, $root);
  291. }
  292. // The number of method arguments allowed for a controller is set by a
  293. // "segments" constant on this class which allows for the developer to
  294. // increase or decrease the limit on method arguments.
  295. $wildcards = static::repeat('(:any?)', static::$segments);
  296. // Once we have the path and root URI we can build a simple route for
  297. // the controller that should handle a conventional controller route
  298. // setup of controller/method/segment/segment, etc.
  299. $pattern = trim("{$root}/{$controller}/{$wildcards}", '/');
  300. // Finally we can build the "uses" clause and the attributes for the
  301. // controller route and register it with the router with a wildcard
  302. // method so it is available on every request method.
  303. $uses = "{$identifier}@(:1)";
  304. $attributes = compact('uses', 'defaults', 'https');
  305. static::register('*', $pattern, $attributes);
  306. }
  307. }
  308. /**
  309. * Register a route for the root of a controller.
  310. *
  311. * @param string $identifier
  312. * @param string $controller
  313. * @param string $root
  314. * @return void
  315. */
  316. protected static function root($identifier, $controller, $root)
  317. {
  318. // First we need to strip "home" off of the controller name to create the
  319. // URI needed to match the controller's folder, which should match the
  320. // root URI we want to point to the index method.
  321. if ($controller !== 'home')
  322. {
  323. $home = dirname($controller);
  324. }
  325. else
  326. {
  327. $home = '';
  328. }
  329. // After we trim the "home" off of the controller name we'll build the
  330. // pattern needed to map to the controller and then register a route
  331. // to point the pattern to the controller's index method.
  332. $pattern = trim($root.'/'.$home, '/') ?: '/';
  333. $attributes = array('uses' => "{$identifier}@index");
  334. static::register('*', $pattern, $attributes);
  335. }
  336. /**
  337. * Find a route by the route's assigned name.
  338. *
  339. * @param string $name
  340. * @return array
  341. */
  342. public static function find($name)
  343. {
  344. if (isset(static::$names[$name])) return static::$names[$name];
  345. // If no route names have been found at all, we will assume no reverse
  346. // routing has been done, and we will load the routes file for all of
  347. // the bundles that are installed for the application.
  348. if (count(static::$names) == 0)
  349. {
  350. foreach (Bundle::names() as $bundle)
  351. {
  352. Bundle::routes($bundle);
  353. }
  354. }
  355. // To find a named route, we will iterate through every route defined
  356. // for the application. We will cache the routes by name so we can
  357. // load them very quickly the next time.
  358. foreach (static::routes() as $method => $routes)
  359. {
  360. foreach ($routes as $key => $value)
  361. {
  362. if (isset($value['as']) and $value['as'] === $name)
  363. {
  364. return static::$names[$name] = array($key => $value);
  365. }
  366. }
  367. }
  368. }
  369. /**
  370. * Find the route that uses the given action.
  371. *
  372. * @param string $action
  373. * @return array
  374. */
  375. public static function uses($action)
  376. {
  377. // If the action has already been reverse routed before, we'll just
  378. // grab the previously found route to save time. They are cached
  379. // in a static array on the class.
  380. if (isset(static::$uses[$action]))
  381. {
  382. return static::$uses[$action];
  383. }
  384. Bundle::routes(Bundle::name($action));
  385. // To find the route, we'll simply spin through the routes looking
  386. // for a route with a "uses" key matching the action, and if we
  387. // find one, we cache and return it.
  388. foreach (static::routes() as $method => $routes)
  389. {
  390. foreach ($routes as $key => $value)
  391. {
  392. if (isset($value['uses']) and $value['uses'] === $action)
  393. {
  394. return static::$uses[$action] = array($key => $value);
  395. }
  396. }
  397. }
  398. }
  399. /**
  400. * Search the routes for the route matching a method and URI.
  401. *
  402. * @param string $method
  403. * @param string $uri
  404. * @return Route
  405. */
  406. public static function route($method, $uri)
  407. {
  408. Bundle::start($bundle = Bundle::handles($uri));
  409. $routes = (array) static::method($method);
  410. // Of course literal route matches are the quickest to find, so we will
  411. // check for those first. If the destination key exists in the routes
  412. // array we can just return that route now.
  413. if (array_key_exists($uri, $routes))
  414. {
  415. $action = $routes[$uri];
  416. return new Route($method, $uri, $action);
  417. }
  418. // If we can't find a literal match we'll iterate through all of the
  419. // registered routes to find a matching route based on the route's
  420. // regular expressions and wildcards.
  421. if ( ! is_null($route = static::match($method, $uri)))
  422. {
  423. return $route;
  424. }
  425. }
  426. /**
  427. * Iterate through every route to find a matching route.
  428. *
  429. * @param string $method
  430. * @param string $uri
  431. * @return Route
  432. */
  433. protected static function match($method, $uri)
  434. {
  435. foreach (static::method($method) as $route => $action)
  436. {
  437. // We only need to check routes with regular expression since all others
  438. // would have been able to be matched by the search for literal matches
  439. // we just did before we started searching.
  440. if (str_contains($route, '('))
  441. {
  442. $pattern = '#^'.static::wildcards($route).'$#u';
  443. // If we get a match we'll return the route and slice off the first
  444. // parameter match, as preg_match sets the first array item to the
  445. // full-text match of the pattern.
  446. if (preg_match($pattern, $uri, $parameters))
  447. {
  448. return new Route($method, $route, $action, array_slice($parameters, 1));
  449. }
  450. }
  451. }
  452. }
  453. /**
  454. * Translate route URI wildcards into regular expressions.
  455. *
  456. * @param string $key
  457. * @return string
  458. */
  459. protected static function wildcards($key)
  460. {
  461. list($search, $replace) = array_divide(static::$optional);
  462. // For optional parameters, first translate the wildcards to their
  463. // regex equivalent, sans the ")?" ending. We'll add the endings
  464. // back on when we know the replacement count.
  465. $key = str_replace($search, $replace, $key, $count);
  466. if ($count > 0)
  467. {
  468. $key .= str_repeat(')?', $count);
  469. }
  470. return strtr($key, static::$patterns);
  471. }
  472. /**
  473. * Get all of the registered routes, with fallbacks at the end.
  474. *
  475. * @return array
  476. */
  477. public static function routes()
  478. {
  479. $routes = static::$routes;
  480. foreach (static::$methods as $method)
  481. {
  482. // It's possible that the routes array may not contain any routes for the
  483. // method, so we'll seed each request method with an empty array if it
  484. // doesn't already contain any routes.
  485. if ( ! isset($routes[$method])) $routes[$method] = array();
  486. $fallback = array_get(static::$fallback, $method, array());
  487. // When building the array of routes, we'll merge in all of the fallback
  488. // routes for each request method individually. This allows us to avoid
  489. // collisions when merging the arrays together.
  490. $routes[$method] = array_merge($routes[$method], $fallback);
  491. }
  492. return $routes;
  493. }
  494. /**
  495. * Grab all of the routes for a given request method.
  496. *
  497. * @param string $method
  498. * @return array
  499. */
  500. public static function method($method)
  501. {
  502. $routes = array_get(static::$routes, $method, array());
  503. return array_merge($routes, array_get(static::$fallback, $method, array()));
  504. }
  505. /**
  506. * Get all of the wildcard patterns
  507. *
  508. * @return array
  509. */
  510. public static function patterns()
  511. {
  512. return array_merge(static::$patterns, static::$optional);
  513. }
  514. /**
  515. * Get a string repeating a URI pattern any number of times.
  516. *
  517. * @param string $pattern
  518. * @param int $times
  519. * @return string
  520. */
  521. protected static function repeat($pattern, $times)
  522. {
  523. return implode('/', array_fill(0, $times, $pattern));
  524. }
  525. }