Rewrite.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Controller
  17. * @subpackage Router
  18. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @version $Id$
  20. * @license http://framework.zend.com/license/new-bsd New BSD License
  21. */
  22. /** Zend_Controller_Router_Abstract */
  23. require_once 'Zend/Controller/Router/Abstract.php';
  24. /** Zend_Controller_Router_Route */
  25. require_once 'Zend/Controller/Router/Route.php';
  26. /**
  27. * Ruby routing based Router.
  28. *
  29. * @package Zend_Controller
  30. * @subpackage Router
  31. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  32. * @license http://framework.zend.com/license/new-bsd New BSD License
  33. * @see http://manuals.rubyonrails.com/read/chapter/65
  34. */
  35. class Zend_Controller_Router_Rewrite extends Zend_Controller_Router_Abstract
  36. {
  37. /**
  38. * Whether or not to use default routes
  39. *
  40. * @var boolean
  41. */
  42. protected $_useDefaultRoutes = true;
  43. /**
  44. * Array of routes to match against
  45. *
  46. * @var array
  47. */
  48. protected $_routes = array();
  49. /**
  50. * Currently matched route
  51. *
  52. * @var string
  53. */
  54. protected $_currentRoute = null;
  55. /**
  56. * Global parameters given to all routes
  57. *
  58. * @var array
  59. */
  60. protected $_globalParams = array();
  61. /**
  62. * Separator to use with chain names
  63. *
  64. * @var string
  65. */
  66. protected $_chainNameSeparator = '-';
  67. /**
  68. * Determines if request parameters should be used as global parameters
  69. * inside this router.
  70. *
  71. * @var boolean
  72. */
  73. protected $_useCurrentParamsAsGlobal = false;
  74. /**
  75. * Add default routes which are used to mimic basic router behaviour
  76. *
  77. * @return Zend_Controller_Router_Rewrite
  78. */
  79. public function addDefaultRoutes()
  80. {
  81. if (!$this->hasRoute('default')) {
  82. $dispatcher = $this->getFrontController()->getDispatcher();
  83. $request = $this->getFrontController()->getRequest();
  84. require_once 'Zend/Controller/Router/Route/Module.php';
  85. $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request);
  86. $this->_routes = array('default' => $compat) + $this->_routes;
  87. }
  88. return $this;
  89. }
  90. /**
  91. * Add route to the route chain
  92. *
  93. * If route contains method setRequest(), it is initialized with a request object
  94. *
  95. * @param string $name Name of the route
  96. * @param Zend_Controller_Router_Route_Interface $route Instance of the route
  97. * @return Zend_Controller_Router_Rewrite
  98. */
  99. public function addRoute($name, Zend_Controller_Router_Route_Interface $route)
  100. {
  101. if (method_exists($route, 'setRequest')) {
  102. $route->setRequest($this->getFrontController()->getRequest());
  103. }
  104. $this->_routes[$name] = $route;
  105. return $this;
  106. }
  107. /**
  108. * Add routes to the route chain
  109. *
  110. * @param array $routes Array of routes with names as keys and routes as values
  111. * @return Zend_Controller_Router_Rewrite
  112. */
  113. public function addRoutes($routes) {
  114. foreach ($routes as $name => $route) {
  115. $this->addRoute($name, $route);
  116. }
  117. return $this;
  118. }
  119. /**
  120. * Create routes out of Zend_Config configuration
  121. *
  122. * Example INI:
  123. * routes.archive.route = "archive/:year/*"
  124. * routes.archive.defaults.controller = archive
  125. * routes.archive.defaults.action = show
  126. * routes.archive.defaults.year = 2000
  127. * routes.archive.reqs.year = "\d+"
  128. *
  129. * routes.news.type = "Zend_Controller_Router_Route_Static"
  130. * routes.news.route = "news"
  131. * routes.news.defaults.controller = "news"
  132. * routes.news.defaults.action = "list"
  133. *
  134. * And finally after you have created a Zend_Config with above ini:
  135. * $router = new Zend_Controller_Router_Rewrite();
  136. * $router->addConfig($config, 'routes');
  137. *
  138. * @param Zend_Config $config Configuration object
  139. * @param string $section Name of the config section containing route's definitions
  140. * @throws Zend_Controller_Router_Exception
  141. * @return Zend_Controller_Router_Rewrite
  142. */
  143. public function addConfig(Zend_Config $config, $section = null)
  144. {
  145. if ($section !== null) {
  146. if ($config->{$section} === null) {
  147. require_once 'Zend/Controller/Router/Exception.php';
  148. throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'");
  149. }
  150. $config = $config->{$section};
  151. }
  152. foreach ($config as $name => $info) {
  153. $route = $this->_getRouteFromConfig($info);
  154. if ($route instanceof Zend_Controller_Router_Route_Chain) {
  155. if (!isset($info->chain)) {
  156. require_once 'Zend/Controller/Router/Exception.php';
  157. throw new Zend_Controller_Router_Exception("No chain defined");
  158. }
  159. if ($info->chain instanceof Zend_Config) {
  160. $childRouteNames = $info->chain;
  161. } else {
  162. $childRouteNames = explode(',', $info->chain);
  163. }
  164. foreach ($childRouteNames as $childRouteName) {
  165. $childRoute = $this->getRoute(trim($childRouteName));
  166. $route->chain($childRoute);
  167. }
  168. $this->addRoute($name, $route);
  169. } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) {
  170. $this->_addChainRoutesFromConfig($name, $route, $info->chains);
  171. } else {
  172. $this->addRoute($name, $route);
  173. }
  174. }
  175. return $this;
  176. }
  177. /**
  178. * Get a route frm a config instance
  179. *
  180. * @param Zend_Config $info
  181. * @return Zend_Controller_Router_Route_Interface
  182. */
  183. protected function _getRouteFromConfig(Zend_Config $info)
  184. {
  185. $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route';
  186. if (!class_exists($class)) {
  187. require_once 'Zend/Loader.php';
  188. Zend_Loader::loadClass($class);
  189. }
  190. $route = call_user_func(array($class, 'getInstance'), $info);
  191. if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) {
  192. $route->isAbstract(true);
  193. }
  194. return $route;
  195. }
  196. /**
  197. * Add chain routes from a config route
  198. *
  199. * @param string $name
  200. * @param Zend_Controller_Router_Route_Interface $route
  201. * @param Zend_Config $childRoutesInfo
  202. * @return void
  203. */
  204. protected function _addChainRoutesFromConfig($name,
  205. Zend_Controller_Router_Route_Interface $route,
  206. Zend_Config $childRoutesInfo)
  207. {
  208. foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) {
  209. if (is_string($childRouteInfo)) {
  210. $childRouteName = $childRouteInfo;
  211. $childRoute = $this->getRoute($childRouteName);
  212. } else {
  213. $childRoute = $this->_getRouteFromConfig($childRouteInfo);
  214. }
  215. if ($route instanceof Zend_Controller_Router_Route_Chain) {
  216. $chainRoute = clone $route;
  217. $chainRoute->chain($childRoute);
  218. } else {
  219. $chainRoute = $route->chain($childRoute);
  220. }
  221. $chainName = $name . $this->_chainNameSeparator . $childRouteName;
  222. if (isset($childRouteInfo->chains)) {
  223. $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains);
  224. } else {
  225. $this->addRoute($chainName, $chainRoute);
  226. }
  227. }
  228. }
  229. /**
  230. * Remove a route from the route chain
  231. *
  232. * @param string $name Name of the route
  233. * @throws Zend_Controller_Router_Exception
  234. * @return Zend_Controller_Router_Rewrite
  235. */
  236. public function removeRoute($name)
  237. {
  238. if (!isset($this->_routes[$name])) {
  239. require_once 'Zend/Controller/Router/Exception.php';
  240. throw new Zend_Controller_Router_Exception("Route $name is not defined");
  241. }
  242. unset($this->_routes[$name]);
  243. return $this;
  244. }
  245. /**
  246. * Remove all standard default routes
  247. *
  248. * @return Zend_Controller_Router_Rewrite
  249. */
  250. public function removeDefaultRoutes()
  251. {
  252. $this->_useDefaultRoutes = false;
  253. return $this;
  254. }
  255. /**
  256. * Check if named route exists
  257. *
  258. * @param string $name Name of the route
  259. * @return boolean
  260. */
  261. public function hasRoute($name)
  262. {
  263. return isset($this->_routes[$name]);
  264. }
  265. /**
  266. * Retrieve a named route
  267. *
  268. * @param string $name Name of the route
  269. * @throws Zend_Controller_Router_Exception
  270. * @return Zend_Controller_Router_Route_Interface Route object
  271. */
  272. public function getRoute($name)
  273. {
  274. if (!isset($this->_routes[$name])) {
  275. require_once 'Zend/Controller/Router/Exception.php';
  276. throw new Zend_Controller_Router_Exception("Route $name is not defined");
  277. }
  278. return $this->_routes[$name];
  279. }
  280. /**
  281. * Retrieve a currently matched route
  282. *
  283. * @throws Zend_Controller_Router_Exception
  284. * @return Zend_Controller_Router_Route_Interface Route object
  285. */
  286. public function getCurrentRoute()
  287. {
  288. if (!isset($this->_currentRoute)) {
  289. require_once 'Zend/Controller/Router/Exception.php';
  290. throw new Zend_Controller_Router_Exception("Current route is not defined");
  291. }
  292. return $this->getRoute($this->_currentRoute);
  293. }
  294. /**
  295. * Retrieve a name of currently matched route
  296. *
  297. * @throws Zend_Controller_Router_Exception
  298. * @return string Route name
  299. */
  300. public function getCurrentRouteName()
  301. {
  302. if (!isset($this->_currentRoute)) {
  303. require_once 'Zend/Controller/Router/Exception.php';
  304. throw new Zend_Controller_Router_Exception("Current route is not defined");
  305. }
  306. return $this->_currentRoute;
  307. }
  308. /**
  309. * Retrieve an array of routes added to the route chain
  310. *
  311. * @return array All of the defined routes
  312. */
  313. public function getRoutes()
  314. {
  315. return $this->_routes;
  316. }
  317. /**
  318. * Find a matching route to the current PATH_INFO and inject
  319. * returning values to the Request object.
  320. *
  321. * @param Zend_Controller_Request_Abstract $request
  322. * @throws Zend_Controller_Router_Exception
  323. * @return Zend_Controller_Request_Abstract Request object
  324. */
  325. public function route(Zend_Controller_Request_Abstract $request)
  326. {
  327. if (!$request instanceof Zend_Controller_Request_Http) {
  328. require_once 'Zend/Controller/Router/Exception.php';
  329. throw new Zend_Controller_Router_Exception('Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object');
  330. }
  331. if ($this->_useDefaultRoutes) {
  332. $this->addDefaultRoutes();
  333. }
  334. // Find the matching route
  335. $routeMatched = false;
  336. foreach (array_reverse($this->_routes, true) as $name => $route) {
  337. // TODO: Should be an interface method. Hack for 1.0 BC
  338. if (method_exists($route, 'isAbstract') && $route->isAbstract()) {
  339. continue;
  340. }
  341. // TODO: Should be an interface method. Hack for 1.0 BC
  342. if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
  343. $match = $request->getPathInfo();
  344. } else {
  345. $match = $request;
  346. }
  347. if ($params = $route->match($match)) {
  348. $this->_setRequestParams($request, $params);
  349. $this->_currentRoute = $name;
  350. $routeMatched = true;
  351. break;
  352. }
  353. }
  354. if (!$routeMatched) {
  355. require_once 'Zend/Controller/Router/Exception.php';
  356. throw new Zend_Controller_Router_Exception('No route matched the request', 404);
  357. }
  358. if($this->_useCurrentParamsAsGlobal) {
  359. $params = $request->getParams();
  360. foreach($params as $param => $value) {
  361. $this->setGlobalParam($param, $value);
  362. }
  363. }
  364. return $request;
  365. }
  366. /**
  367. * Sets parameters for request object
  368. *
  369. * Module name, controller name and action name
  370. *
  371. * @param Zend_Controller_Request_Abstract $request
  372. * @param array $params
  373. */
  374. protected function _setRequestParams($request, $params)
  375. {
  376. foreach ($params as $param => $value) {
  377. $request->setParam($param, $value);
  378. if ($param === $request->getModuleKey()) {
  379. $request->setModuleName($value);
  380. }
  381. if ($param === $request->getControllerKey()) {
  382. $request->setControllerName($value);
  383. }
  384. if ($param === $request->getActionKey()) {
  385. $request->setActionName($value);
  386. }
  387. }
  388. }
  389. /**
  390. * Generates a URL path that can be used in URL creation, redirection, etc.
  391. *
  392. * @param array $userParams Options passed by a user used to override parameters
  393. * @param mixed $name The name of a Route to use
  394. * @param bool $reset Whether to reset to the route defaults ignoring URL params
  395. * @param bool $encode Tells to encode URL parts on output
  396. * @throws Zend_Controller_Router_Exception
  397. * @return string Resulting absolute URL path
  398. */
  399. public function assemble($userParams, $name = null, $reset = false, $encode = true)
  400. {
  401. if (!is_array($userParams)) {
  402. require_once 'Zend/Controller/Router/Exception.php';
  403. throw new Zend_Controller_Router_Exception('userParams must be an array');
  404. }
  405. if ($name == null) {
  406. try {
  407. $name = $this->getCurrentRouteName();
  408. } catch (Zend_Controller_Router_Exception $e) {
  409. $name = 'default';
  410. }
  411. }
  412. // Use UNION (+) in order to preserve numeric keys
  413. $params = $userParams + $this->_globalParams;
  414. $route = $this->getRoute($name);
  415. $url = $route->assemble($params, $reset, $encode);
  416. if (!preg_match('|^[a-z]+://|', $url)) {
  417. $url = rtrim($this->getFrontController()->getBaseUrl(), self::URI_DELIMITER) . self::URI_DELIMITER . $url;
  418. }
  419. return $url;
  420. }
  421. /**
  422. * Set a global parameter
  423. *
  424. * @param string $name
  425. * @param mixed $value
  426. * @return Zend_Controller_Router_Rewrite
  427. */
  428. public function setGlobalParam($name, $value)
  429. {
  430. $this->_globalParams[$name] = $value;
  431. return $this;
  432. }
  433. /**
  434. * Set the separator to use with chain names
  435. *
  436. * @param string $separator The separator to use
  437. * @return Zend_Controller_Router_Rewrite
  438. */
  439. public function setChainNameSeparator($separator) {
  440. $this->_chainNameSeparator = $separator;
  441. return $this;
  442. }
  443. /**
  444. * Get the separator to use for chain names
  445. *
  446. * @return string
  447. */
  448. public function getChainNameSeparator() {
  449. return $this->_chainNameSeparator;
  450. }
  451. /**
  452. * Determines/returns whether to use the request parameters as global parameters.
  453. *
  454. * @param boolean|null $use
  455. * Null/unset when you want to retrieve the current state.
  456. * True when request parameters should be global, false otherwise
  457. * @return boolean|Zend_Controller_Router_Rewrite
  458. * Returns a boolean if first param isn't set, returns an
  459. * instance of Zend_Controller_Router_Rewrite otherwise.
  460. *
  461. */
  462. public function useRequestParametersAsGlobal($use = null) {
  463. if($use === null) {
  464. return $this->_useCurrentParamsAsGlobal;
  465. }
  466. $this->_useCurrentParamsAsGlobal = (bool) $use;
  467. return $this;
  468. }
  469. }