/library/Router.php

https://github.com/arsakiyo/apify-library · PHP · 418 lines · 249 code · 51 blank · 118 comment · 57 complexity · 10f0500923fc17e929c40edc84cee4ab MD5 · raw file

  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  6. * @license http://framework.zend.com/license/new-bsd New BSD License
  7. */
  8. class Router
  9. {
  10. /**
  11. * @var array Array of routes to match against
  12. */
  13. protected $routes = array();
  14. /**
  15. * Add routes to the route chain
  16. *
  17. * @param array $routes Array of routes with names as keys and routes as values
  18. */
  19. public function addRoutes($routes)
  20. {
  21. foreach ($routes as $name => $route) {
  22. $this->routes[$name] = $route;
  23. }
  24. }
  25. /**
  26. * Check if named route exists
  27. *
  28. * @param string $name Name of the route
  29. * @return boolean
  30. */
  31. public function hasRoute($name)
  32. {
  33. return isset($this->_routes[$name]);
  34. }
  35. /**
  36. * Retrieve a named route
  37. *
  38. * @param string $name Name of the route
  39. * @return Route object
  40. * @throws RuntimeException
  41. */
  42. public function getRoute($name)
  43. {
  44. if (!isset($this->routes[$name])) {
  45. throw new RuntimeException("Route $name is not defined");
  46. }
  47. return $this->routes[$name];
  48. }
  49. /**
  50. * Retrieve an array of routes added to the route chain
  51. *
  52. * @return array All of the defined routes
  53. */
  54. public function getRoutes()
  55. {
  56. return $this->routes;
  57. }
  58. /**
  59. * Find a matching route to the current url path and inject
  60. * returning values to the Request object.
  61. *
  62. * @return Request object
  63. */
  64. public function route(Request $request)
  65. {
  66. if (! $this->hasRoute('default')) {
  67. $route = array('controller' => 'index', 'action' => 'index');
  68. $compat = new RouteStatic('default', $route);
  69. $this->routes = array_merge(array('default' => $compat), $this->routes);
  70. }
  71. $urlPath = $request->getUrlPath();
  72. foreach (array_reverse($this->routes) as $name => $route) {
  73. if ($params = $route->match($urlPath)) {
  74. $this->setRequestParams($request, $params);
  75. break;
  76. }
  77. }
  78. return $request;
  79. }
  80. public function setRequestParams($request, $params)
  81. {
  82. foreach ($params as $param => $value) {
  83. $request->setParam($param, $value);
  84. if ($param === 'controller') {
  85. $request->setController($value);
  86. }
  87. if ($param === 'action') {
  88. $request->setAction($value);
  89. }
  90. }
  91. }
  92. }
  93. /**
  94. * Zend Framework
  95. *
  96. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  97. * @license http://framework.zend.com/license/new-bsd New BSD License
  98. */
  99. class Route
  100. {
  101. protected $urlVariable = ':';
  102. protected $urlDelimiter = '/';
  103. protected $regexDelimiter = '#';
  104. protected $defaultRegex = null;
  105. protected $parts;
  106. protected $defaults = array();
  107. protected $requirements = array();
  108. protected $staticCount = 0;
  109. protected $vars = array();
  110. protected $params = array();
  111. protected $values = array();
  112. /**
  113. * Prepares the route for mapping by splitting (exploding) it
  114. * to a corresponding atomic parts. These parts are assigned
  115. * a position which is later used for matching and preparing values.
  116. *
  117. * @param string $route Map used to match with later submitted URL path
  118. * @param array $defaults Defaults for map variables with keys as variable names
  119. * @param array $reqs Regular expression requirements for variables (keys as variable names)
  120. */
  121. public function __construct($route, $defaults = array(), $reqs = array())
  122. {
  123. $route = trim($route, $this->urlDelimiter);
  124. $this->defaults = (array) $defaults;
  125. $this->requirements = (array) $reqs;
  126. if ($route != '') {
  127. foreach (explode($this->urlDelimiter, $route) as $pos => $part) {
  128. if (substr($part, 0, 1) == $this->urlVariable) {
  129. $name = substr($part, 1);
  130. $regex = (isset($reqs[$name]) ? $reqs[$name] : $this->defaultRegex);
  131. $this->parts[$pos] = array('name' => $name, 'regex' => $regex);
  132. $this->vars[] = $name;
  133. } else {
  134. $this->parts[$pos] = array('regex' => $part);
  135. if ($part != '*') {
  136. $this->staticCount++;
  137. }
  138. }
  139. }
  140. }
  141. }
  142. protected function getWildcardData($parts, $unique)
  143. {
  144. $pos = count($parts);
  145. if ($pos % 2) {
  146. $parts[] = null;
  147. }
  148. foreach(array_chunk($parts, 2) as $part) {
  149. list($var, $value) = $part;
  150. $var = urldecode($var);
  151. if (!array_key_exists($var, $unique)) {
  152. $this->params[$var] = urldecode($value);
  153. $unique[$var] = true;
  154. }
  155. }
  156. }
  157. /**
  158. * Matches a user submitted path with parts defined by a map. Assigns and
  159. * returns an array of variables on a successful match.
  160. *
  161. * @param string $path Path used to match against this routing map
  162. * @return array|false An array of assigned values or a false on a mismatch
  163. */
  164. public function match($path)
  165. {
  166. $pathStaticCount = 0;
  167. $defaults = $this->defaults;
  168. if (count($defaults)) {
  169. $unique = array_combine(array_keys($defaults), array_fill(0, count($defaults), true));
  170. } else {
  171. $unique = array();
  172. }
  173. $path = trim($path, $this->urlDelimiter);
  174. if ($path != '') {
  175. $path = explode($this->urlDelimiter, $path);
  176. foreach ($path as $pos => $pathPart) {
  177. if (!isset($this->parts[$pos])) {
  178. return false;
  179. }
  180. if ($this->parts[$pos]['regex'] == '*') {
  181. $parts = array_slice($path, $pos);
  182. $this->getWildcardData($parts, $unique);
  183. break;
  184. }
  185. $part = $this->parts[$pos];
  186. $name = isset($part['name']) ? $part['name'] : null;
  187. $pathPart = urldecode($pathPart);
  188. if ($name === null) {
  189. if ($part['regex'] != $pathPart) {
  190. return false;
  191. }
  192. } elseif ($part['regex'] === null) {
  193. if (strlen($pathPart) == 0) {
  194. return false;
  195. }
  196. } else {
  197. $regex = $this->regexDelimiter . '^' . $part['regex'] . '$' . $this->regexDelimiter . 'iu';
  198. if (!preg_match($regex, $pathPart)) {
  199. return false;
  200. }
  201. }
  202. if ($name !== null) {
  203. // It's a variable. Setting a value
  204. $this->values[$name] = $pathPart;
  205. $unique[$name] = true;
  206. } else {
  207. $pathStaticCount++;
  208. }
  209. }
  210. }
  211. $return = $this->values + $this->params + $this->defaults;
  212. // Check if all static mappings have been met
  213. if ($this->staticCount != $pathStaticCount) {
  214. return false;
  215. }
  216. // Check if all map variables have been initialized
  217. foreach ($this->vars as $var) {
  218. if (!array_key_exists($var, $return)) {
  219. return false;
  220. }
  221. }
  222. return $return;
  223. }
  224. /**
  225. * Assembles user submitted parameters forming a URL path defined by this route
  226. *
  227. * @param array $data An array of variable and value pairs used as parameters
  228. * @param boolean $reset Whether or not to set route defaults with those provided in $data
  229. * @return string Route path with user submitted parameters
  230. * @throws RuntimeException
  231. */
  232. public function assemble($data = array(), $reset = false)
  233. {
  234. $url = array();
  235. $flag = false;
  236. foreach ($this->parts as $key => $part) {
  237. $resetPart = false;
  238. if (isset($part['name']) && array_key_exists($part['name'], $data) && $data[$part['name']] === null) {
  239. $resetPart = true;
  240. }
  241. if (isset($part['name'])) {
  242. if (isset($data[$part['name']]) && !$resetPart) {
  243. $url[$key] = $data[$part['name']];
  244. unset($data[$part['name']]);
  245. } elseif (!$reset && !$resetPart && isset($this->values[$part['name']])) {
  246. $url[$key] = $this->values[$part['name']];
  247. } elseif (!$reset && !$resetPart && isset($this->params[$part['name']])) {
  248. $url[$key] = $this->params[$part['name']];
  249. } elseif (isset($this->defaults[$part['name']])) {
  250. $url[$key] = $this->defaults[$part['name']];
  251. } else {
  252. throw new RuntimeException($part['name'] . ' is not specified');
  253. }
  254. } else {
  255. if ($part['regex'] != '*') {
  256. $url[$key] = $part['regex'];
  257. } else {
  258. if (!$reset) $data += $this->params;
  259. foreach ($data as $var => $value) {
  260. if ($value !== null) {
  261. $url[$var] = $var . $this->urlDelimiter . $value;
  262. $flag = true;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. $return = '';
  269. foreach (array_reverse($url, true) as $key => $value) {
  270. if ($flag || !isset($this->parts[$key]['name']) || $value !== $this->getDefault($this->parts[$key]['name'])) {
  271. $return = $this->urlDelimiter . $value . $return;
  272. $flag = true;
  273. }
  274. }
  275. return trim($return, $this->urlDelimiter);
  276. }
  277. /**
  278. * Return a single parameter of route's defaults
  279. *
  280. * @param string $name Array key of the parameter
  281. * @return string Previously set default
  282. */
  283. public function getDefault($name)
  284. {
  285. if (isset($this->defaults[$name])) {
  286. return $this->defaults[$name];
  287. }
  288. return null;
  289. }
  290. /**
  291. * Return an array of defaults
  292. *
  293. * @return array Route defaults
  294. */
  295. public function getDefaults()
  296. {
  297. return $this->defaults;
  298. }
  299. }
  300. /**
  301. * Zend Framework
  302. *
  303. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  304. * @license http://framework.zend.com/license/new-bsd New BSD License
  305. */
  306. class RouteStatic
  307. {
  308. protected $route = null;
  309. protected $defaults = array();
  310. /**
  311. * Prepares the route for mapping.
  312. *
  313. * @param string $route Map used to match with later submitted URL path
  314. * @param array $defaults Defaults for map variables with keys as variable names
  315. */
  316. public function __construct($route, $defaults = array())
  317. {
  318. $this->route = trim($route, '/');
  319. $this->defaults = (array) $defaults;
  320. }
  321. /**
  322. * Matches a user submitted path with a previously defined route.
  323. * Assigns and returns an array of defaults on a successful match.
  324. *
  325. * @param string $path Path used to match against this routing map
  326. * @return array|false An array of assigned values or a false on a mismatch
  327. */
  328. public function match($path)
  329. {
  330. if (trim($path, '/') == $this->route) {
  331. return $this->defaults;
  332. }
  333. return false;
  334. }
  335. /**
  336. * Assembles a URL path defined by this route
  337. *
  338. * @param array $data An array of variable and value pairs used as parameters
  339. * @return string Route path with user submitted parameters
  340. */
  341. public function assemble($data = array())
  342. {
  343. return $this->route;
  344. }
  345. /**
  346. * Return a single parameter of route's defaults
  347. *
  348. * @param string $name Array key of the parameter
  349. * @return string Previously set default
  350. */
  351. public function getDefault($name)
  352. {
  353. if (isset($this->defaults[$name])) {
  354. return $this->defaults[$name];
  355. }
  356. return null;
  357. }
  358. /**
  359. * Return an array of defaults
  360. *
  361. * @return array Route defaults
  362. */
  363. public function getDefaults()
  364. {
  365. return $this->defaults;
  366. }
  367. }