PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/lithium/net/http/Router.php

https://github.com/shaunxcode/comicengine
PHP | 323 lines | 122 code | 21 blank | 180 comment | 24 complexity | 858de189845945d944bd9d22f16660e7 MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\net\http;
  9. use lithium\util\Inflector;
  10. use lithium\net\http\RoutingException;
  11. /**
  12. * The two primary responsibilities of the `Router` class are to generate URLs from parameter lists,
  13. * and to determine the correct set of dispatch parameters for incoming requests.
  14. *
  15. * Using `Route` objects, these two operations can be handled in a reciprocally consistent way.
  16. * For example, if you wanted the `/login` URL to be routed to
  17. * `myapp\controllers\SessionsController::add()`, you could set up a route like the following in
  18. * `config/routes.php`:
  19. *
  20. * {{{
  21. * use lithium\net\http\Router;
  22. *
  23. * Router::connect('/login', array('controller' => 'sessions', 'action' => 'add'));
  24. *
  25. * // -- or --
  26. *
  27. * Router::connect('/login', 'Sessions::add');
  28. * }}}
  29. *
  30. * Not only would that correctly route all requests for `/login` to `SessionsController::add()`, but
  31. * any time the framework generated a route with matching parameters, `Router` would return the
  32. * correct short URL.
  33. *
  34. * While most framework components that work with URLs (and utilize routing) handle calling the
  35. * `Router` directly (i.e. controllers doing redirects, or helpers generating links), if you have a
  36. * scenario where you need to call the `Router` directly, you can use the `match()` method.
  37. *
  38. * This allows you to keep your application's URL structure nicely decoupled from the underlying
  39. * software design. For more information on parsing and generating URLs, see the `parse()` and
  40. * `match()` methods.
  41. */
  42. class Router extends \lithium\core\StaticObject {
  43. /**
  44. * An array of loaded lithium\net\http\Route objects used to match Request objects against.
  45. *
  46. * @var array
  47. */
  48. protected static $_configurations = array();
  49. /**
  50. * Classes used by `Router`.
  51. *
  52. * @var array
  53. */
  54. protected static $_classes = array(
  55. 'route' => 'lithium\net\http\Route'
  56. );
  57. public static function config($config = array()) {
  58. if (!$config) {
  59. return array('classes' => static::$_classes);
  60. }
  61. if (isset($config['classes'])) {
  62. static::$_classes = $config['classes'] + static::$_classes;
  63. }
  64. }
  65. /**
  66. * Connects a new route and returns the current routes array. This method creates a new
  67. * `Route` object and registers it with the `Router`. The order in which routes are connected
  68. * matters, since the order of precedence is taken into account in parsing and matching
  69. * operations.
  70. *
  71. * @see lithium\net\http\Route
  72. * @see lithium\net\http\Router::parse()
  73. * @see lithium\net\http\Router::match()
  74. * @param string $template An empty string, or a route string "/"
  75. * @param array $params An array describing the default or required elements of the route
  76. * @param array $options
  77. * @return array Array of routes
  78. */
  79. public static function connect($template, $params = array(), $options = array()) {
  80. if (!is_object($template)) {
  81. if (is_string($params)) {
  82. $params = static::_parseString($params, false);
  83. }
  84. if (isset($params[0]) && is_array($tmp = static::_parseString($params[0], false))) {
  85. unset($params[0]);
  86. $params = $tmp + $params;
  87. }
  88. $params += array('action' => 'index');
  89. if (is_callable($options)) {
  90. $options = array('handler' => $options);
  91. }
  92. $class = static::$_classes['route'];
  93. $template = new $class(compact('template', 'params') + $options);
  94. }
  95. return (static::$_configurations[] = $template);
  96. }
  97. /**
  98. * Wrapper method which takes a `Request` object, parses it through all attached `Route`
  99. * objects, and assigns the resulting parameters to the `Request` object, and returning it.
  100. *
  101. * @param object $request A request object, usually an instance of `lithium\action\Request`.
  102. * @return object Returns a copy of the `Request` object with parameters applied.
  103. */
  104. public static function process($request) {
  105. if (!$result = static::parse($request)) {
  106. return $request;
  107. }
  108. return $result;
  109. }
  110. /**
  111. * Accepts an instance of `lithium\action\Request` (or a subclass) and matches it against each
  112. * route, in the order that the routes are connected.
  113. *
  114. * @see lithium\action\Request
  115. * @see lithium\net\http\Router::connect()
  116. * @param object $request A request object containing URL and environment data.
  117. * @return array Returns an array of parameters specifying how the given request should be
  118. * routed. The keys returned depend on the `Route` object that was matched, but
  119. * typically include `'controller'` and `'action'` keys.
  120. */
  121. public static function parse($request) {
  122. foreach (static::$_configurations as $route) {
  123. if ($match = $route->parse($request)) {
  124. return $match;
  125. }
  126. }
  127. }
  128. /**
  129. * Attempts to match an array of route parameters (i.e. `'controller'`, `'action'`, etc.)
  130. * against a connected `Route` object. For example, given the following route:
  131. *
  132. * {{{
  133. * Router::connect('/login', array('controller' => 'users', 'action' => 'login'));
  134. * }}}
  135. *
  136. * This will match:
  137. * {{{
  138. * $url = Router::match(array('controller' => 'users', 'action' => 'login'));
  139. * // returns /login
  140. * }}}
  141. *
  142. * For URLs templates with no insert parameters (i.e. elements like `{:id}` that are replaced
  143. * with a value), all parameters must match exactly as they appear in the route parameters.
  144. *
  145. * Alternatively to using a full array, you can specify routes using a more compact syntax. The
  146. * above example can be written as:
  147. *
  148. * {{{ $url = Router::match('Users::login'); // still returns /login }}}
  149. *
  150. * You can combine this with more complicated routes; for example:
  151. * {{{
  152. * Router::connect('/posts/{:id:\d+}', array('controller' => 'posts', 'action' => 'view'));
  153. * }}}
  154. *
  155. * This will match:
  156. * {{{
  157. * $url = Router::match(array('controller' => 'posts', 'action' => 'view', 'id' => '1138'));
  158. * // returns /posts/1138
  159. * }}}
  160. *
  161. * Again, you can specify the same URL with a more compact syntax, as in the following:
  162. * {{{
  163. * $url = Router::match(array('Posts::view', 'id' => '1138'));
  164. * // again, returns /posts/1138
  165. * }}}
  166. *
  167. * You can use either syntax anywhere a URL is accepted, i.e.
  168. * `lithium\action\Controller::redirect()`, or `lithium\template\helper\Html::link()`.
  169. *
  170. * @param string|array $url Options to match to a URL. Optionally, this can be a string
  171. * containing a manually generated URL.
  172. * @param object $context An instance of `lithium\action\Request`. This supplies the context for
  173. * any persistent parameters, as well as the base URL for the application.
  174. * @param array $options Options for the generation of the matched URL. Currently accepted
  175. * values are:
  176. * - `'absolute'` _boolean_: Indicates whether or not the returned URL should be an
  177. * absolute path (i.e. including scheme and host name).
  178. * - `'host'` _string_: If `'absolute'` is `true`, sets the host name to be used,
  179. * or overrides the one provided in `$context`.
  180. * - `'scheme'` _string_: If `'absolute'` is `true`, sets the URL scheme to be
  181. * used, or overrides the one provided in `$context`.
  182. * @return string Returns a generated URL, based on the URL template of the matched route, and
  183. * prefixed with the base URL of the application.
  184. */
  185. public static function match($url = array(), $context = null, array $options = array()) {
  186. if (is_string($url)) {
  187. if (strpos($url, '#') === 0 || strpos($url, 'mailto') === 0 || strpos($url, '://')) {
  188. return $url;
  189. }
  190. if (is_string($url = static::_parseString($url, $context))) {
  191. return static::_prefix($url, $context, $options);
  192. }
  193. }
  194. if (isset($url[0]) && is_array($params = static::_parseString($url[0], $context))) {
  195. unset($url[0]);
  196. $url = $params + $url;
  197. }
  198. $url = static::_persist($url, $context);
  199. $defaults = array('action' => 'index');
  200. $url += $defaults;
  201. $base = isset($context) ? $context->env('base') : '';
  202. $suffix = isset($url['#']) ? "#{$url['#']}" : null;
  203. unset($url['#']);
  204. foreach (static::$_configurations as $route) {
  205. if (!$match = $route->match($url, $context)) {
  206. continue;
  207. }
  208. $path = rtrim("{$base}{$match}{$suffix}", '/') ?: '/';
  209. $path = ($options) ? static::_prefix($path, $context, $options) : $path;
  210. return $path ?: '/';
  211. }
  212. $match = array("\n", 'array (', ',)', '=> NULL', '( \'', ', ');
  213. $replace = array('', '(', ')', '=> null', '(\'', ', ');
  214. $url = str_replace($match, $replace, var_export($url, true));
  215. throw new RoutingException("No parameter match found for URL `{$url}`.");
  216. }
  217. /**
  218. * Returns the prefix (scheme + hostname) for a URL based on the passed `$options` and the
  219. * `$context`.
  220. *
  221. * @param string $path The URL to be prefixed.
  222. * @param object $context The request context.
  223. * @param array $options Options for generating the proper prefix. Currently accepted values
  224. * are: `'absolute' => true|false`, `'host' => string` and `'scheme' => string`.
  225. * @return string The prefixed URL, depending on the passed options.
  226. */
  227. protected static function _prefix($path, $context = null, array $options = array()) {
  228. $defaults = array('scheme' => null, 'host' => null, 'absolute' => false);
  229. if ($context) {
  230. $defaults['host'] = $context->env('HTTP_HOST');
  231. $defaults['scheme'] = $context->env('HTTPS') ? 'https://' : 'http://';
  232. }
  233. $options += $defaults;
  234. return ($options['absolute']) ? "{$options['scheme']}{$options['host']}{$path}" : $path;
  235. }
  236. /**
  237. * Copies persistent parameters (parameters in the request which have been designated to
  238. * persist) to the current URL, unless the parameter has been explicitly disabled from
  239. * persisting by setting the value in the URL to `null`, or by assigning some other value.
  240. *
  241. * For example:
  242. *
  243. * {{{ embed:lithium\tests\cases\net\http\RouterTest::testParameterPersistence(1-10) }}}
  244. *
  245. * @see lithium\action\Request::$persist
  246. * @param array $url The parameters that define the URL to be matched.
  247. * @param object $context Typically an instance of `lithium\action\Request`, which contains a
  248. * `$persist` property, which is an array of keys to be persisted in URLs between
  249. * requests.
  250. * @return array Returns the modified URL array.
  251. */
  252. protected static function _persist($url, $context) {
  253. if (!$context || !isset($context->persist)) {
  254. return $url;
  255. }
  256. foreach ($context->persist as $key) {
  257. $url += array($key => $context->params[$key]);
  258. if ($url[$key] === null) {
  259. unset($url[$key]);
  260. }
  261. }
  262. return $url;
  263. }
  264. /**
  265. * Returns a route from the loaded configurations, by name.
  266. *
  267. * @param string $route Name of the route to request.
  268. * @return lithium\net\http\Route
  269. */
  270. public static function get($route = null) {
  271. if ($route === null) {
  272. return static::$_configurations;
  273. }
  274. return isset(static::$_configurations[$route]) ? static::$_configurations[$route] : null;
  275. }
  276. /**
  277. * Resets the `Router` to its default state, unloading all routes.
  278. *
  279. * @return void
  280. */
  281. public static function reset() {
  282. static::$_configurations = array();
  283. }
  284. /**
  285. * Helper function for taking a path string and parsing it into a controller and action array.
  286. *
  287. * @param string $path Path string to parse.
  288. * @param boolean $context
  289. * @return array
  290. */
  291. protected static function _parseString($path, $context) {
  292. if (!preg_match('/^[A-Za-z0-9_]+::[A-Za-z0-9_]+$/', $path)) {
  293. $base = $context ? $context->env('base') : '';
  294. $path = trim($path, '/');
  295. return $context !== false ? "{$base}/{$path}" : null;
  296. }
  297. list($controller, $action) = explode('::', $path, 2);
  298. $controller = Inflector::underscore($controller);
  299. return compact('controller', 'action');
  300. }
  301. }
  302. ?>