PageRenderTime 21ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/madeam/Router.php

http://github.com/joshdavey/madeam
PHP | 236 lines | 110 code | 29 blank | 97 comment | 24 complexity | d54ad40819781b6e9e95b209ec9fc8b9 MD5 | raw file
  1. <?php
  2. namespace madeam;
  3. /**
  4. * Madeam PHP Framework <http://madeam.com>
  5. * Copyright (c) 2009, Joshua Davey
  6. * 202-212 Adeliade St. W, Toronto, Ontario, Canada
  7. *
  8. * Licensed under The MIT License
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) 2009, Joshua Davey
  12. * @link http://www.madeam.com
  13. * @package madeam
  14. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  15. */
  16. class Router {
  17. public static $routes = array(); // regex, values, rules
  18. static public $defaults = array(
  19. '_controller' => 'index',
  20. '_action' => 'index',
  21. '_format' => 'html',
  22. '_method' => 'get',
  23. '_layout' => '0',
  24. '_ajax' => '0'
  25. );
  26. /**
  27. * This cool method adds paths by formatting the string the user has entered and turning it into a regular expression
  28. * which can be used to be compared against URIs.
  29. *
  30. * @param string $route
  31. * @param array $params
  32. * @author Joshua Davey
  33. */
  34. public static function connect($route, $values = array(), $rules = array()) {
  35. // break into pieces/bits
  36. $bits = explode('/', $route);
  37. $regex = null;
  38. // parse each bit into it's regular expression form
  39. foreach ($bits as $bit) {
  40. if (preg_match('/^:([a-zA-Z_]+)$/', $bit, $match)) {
  41. // named parameter
  42. $name = $match[1];
  43. if (isset($rules[$name])) {
  44. // named param with a rule
  45. $regex .= '(?:\\/(?P<' . $name . '>' . $rules[$name] . '))';
  46. // we don't need the rule anymore if its in the regex
  47. unset($rules[$name]);
  48. } else {
  49. // named param with no rules
  50. $regex .= '(?:\\/(?P<' . $name . '>' . '[^\/]+))?';
  51. }
  52. } else {
  53. // a string
  54. $regex .= '\\/' . $bit;
  55. }
  56. }
  57. // build route's regexp
  58. $regex = '/^' . $regex . '\/?(?P<_extra>.*)$/';
  59. // add to routes list
  60. self::$routes[] = array($regex, $values, $rules);
  61. }
  62. /**
  63. * Default routes for a RESTful resource.
  64. *
  65. * To use a slug instead of an id or to simply change the rule for the id the $id param
  66. * has a value for the id's name and the id's rule. For example if I wanted to call "id"
  67. * "slug" and change the rule to one that matches slugs I could do the following:
  68. *
  69. * madeam\Router::resource::('posts', array('id' => 'slug', 'pattern' => '[a-z0-9\-]+'));
  70. *
  71. * @param string $name
  72. * @param array $options
  73. * @param string controller
  74. * @param string id
  75. * @param string pattern
  76. * @author Joshua Davey
  77. */
  78. public static function resource($name, $options = array()) {
  79. $controller = isset($options['controller']) ? $options['controller'] : $name;
  80. $id = isset($options['id']) ? $options['id'] : 'id';
  81. $pattern = isset($options['pattern']) ? $options['pattern'] : '\d+';
  82. self::connect("$name/:$id[0]", array('_action' => 'show', '_controller' => $name), array('_method' => 'get', $id[0] => $id[1]));
  83. self::connect("$name/edit/:$id[0]", array('_action' => 'edit', '_controller' => $name), array('_method' => 'get', $id[0] => $id[1]));
  84. self::connect("$name/new", array('_action' => 'new', '_controller' => $name), array('_method' => 'get'));
  85. self::connect("$name", array('_action' => 'delete', '_controller' => $name), array('_method' => 'delete'));
  86. self::connect("$name", array('_action' => 'update', '_controller' => $name), array('_method' => 'put'));
  87. self::connect("$name", array('_action' => 'create', '_controller' => $name), array('_method' => 'post'));
  88. self::connect("$name", array('_action' => 'index', '_controller' => $name), array('_method' => 'get'));
  89. }
  90. /**
  91. * This method takes a URL and parses it for parameters
  92. *
  93. * Parameters (params) can be passed to the framework by adding a get query to the end of a url like so: ?foo=bar
  94. * Or by defining params in the routes configuration file @see config/routes.php
  95. *
  96. * If no values have been assigned to madeam's special params then default values are assigned
  97. * which can be defined in the configuration @see config/setup.php
  98. *
  99. * This method excepts URIs in anyformat.
  100. * Examples:
  101. * index/test?blah=nah
  102. *
  103. * @param string $uri
  104. * @return array
  105. * @author Joshua Davey
  106. */
  107. public static function parse($uri = false, $baseUri = '/') {
  108. // makes sure the first character is "/"
  109. // if (substr($uri, 0, 1) != '/') { $uri = '/' . $uri; }
  110. substr($uri, 0, 1) == '/' ?: $uri = '/' . $uri;
  111. $defaults = self::$defaults;
  112. // parse uri
  113. $parsedURI = parse_url($uri);
  114. // set uri
  115. if (isset($parsedURI['path']) && $baseUri == '/') {
  116. $uri = $parsedURI['path'];
  117. } elseif (isset($parsedURI['path'])) {
  118. // we do an explode because we can't always expect the uri path to be included
  119. // it's normally only inlcuded during the original request and all sub calls
  120. // consist of just the uri without the path to the uri
  121. // for example on request: /madeam/posts/view/32
  122. // and during the request in a controller: /posts/view/32
  123. $extractedPath = explode($baseUri, $parsedURI['path'], 2);
  124. $uri = array_pop($extractedPath);
  125. } else {
  126. $uri = null;
  127. }
  128. $format = false;
  129. // grab format if it exists in the uri and strip it from the uri
  130. // index.html => format = 'html' && uri = 'index'
  131. $dotPosition = (int) strrpos($uri, '.');
  132. if ($dotPosition !== 0) {
  133. $format = substr($uri, $dotPosition + 1);
  134. $uri = substr($uri, 0, $dotPosition);
  135. }
  136. // set get
  137. $get = array();
  138. if (isset($parsedURI['query'])) {
  139. $query = $parsedURI['query'];
  140. // retrieve $_GET vars manually from uri -- so we can enter the uri as index/index?foo=bar when calling a component from the view
  141. parse_str($query, $get); // assigns $get array of query params
  142. }
  143. // set default format
  144. // !$format ?: $defaults['_format'] = $format; // for some reason this line of code will work for a few requests and then crash... replaced with the code below
  145. if ($format !== false) {
  146. $defaults['_format'] = $format;
  147. }
  148. // add query to params
  149. !isset($query) ?: $defaults['_query'] = $query;
  150. // matchs count
  151. $matchs = 0;
  152. // match uri to route map
  153. foreach (self::$routes as $route) {
  154. if (preg_match($route[0], $uri, $match)) {
  155. // set default params
  156. $params = $route[1]; // default values
  157. $rules = $route[2]; // param rules
  158. // set _uri param for websites that don't have mod_rewrite
  159. // sites with mod_rewrite have _uri assigned automatically in the .htaccess file
  160. $params['_uri'] = $match[0];
  161. // clean param matchs by removing nulls and preg_match's numbered results
  162. // every other match is a numbered preg_match result
  163. $index = 0;
  164. foreach ($match as $key => $val) {
  165. if ($val == null) {
  166. unset($match[$key]); // remove null values in order for the default values to work
  167. } elseif (++$index % 2) {
  168. unset($match[$key]); // remove preg_match's numbered results
  169. }
  170. }
  171. // merge default param values with matched params
  172. $params = array_merge($defaults, $params, $match);
  173. // check each rule against its associated param.
  174. // if it fails then we break out of this loop and continue to the next route
  175. $continue = false;
  176. foreach ($rules as $rule => $val) {
  177. if (!isset($params[$rule]) || $params[$rule] !== $val) {
  178. $continue = true;
  179. break;
  180. }
  181. }
  182. if ($continue === true) { continue; }
  183. // flag as matched
  184. $matchs++;
  185. // we've found our match and now we're done here
  186. break;
  187. }
  188. }
  189. if ($matchs == 0) {
  190. // this is lame and needs to be done better
  191. //header("HTTP/1.1 404 Not Found");
  192. //ob_clean();
  193. throw new Exception('Unable to find page');
  194. // but what about returning the params if we throw an error?
  195. return $params;
  196. }
  197. // get params from uri
  198. $params = array_merge($defaults, $params, $get);
  199. // automagically disable the layout when making an AJAX call
  200. if (!isset($params['_layout']) && isset($params['_ajax']) && $params['_ajax'] == 1) {
  201. $params['_layout'] = 0;
  202. } elseif (!isset($params['_layout'])) {
  203. $params['_layout'] = 1;
  204. }
  205. return $params;
  206. }
  207. }