Rewrite.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. {
  115. foreach ($routes as $name => $route) {
  116. $this->addRoute($name, $route);
  117. }
  118. return $this;
  119. }
  120. /**
  121. * Create routes out of Zend_Config configuration
  122. *
  123. * Example INI:
  124. * routes.archive.route = "archive/:year/*"
  125. * routes.archive.defaults.controller = archive
  126. * routes.archive.defaults.action = show
  127. * routes.archive.defaults.year = 2000
  128. * routes.archive.reqs.year = "\d+"
  129. *
  130. * routes.news.type = "Zend_Controller_Router_Route_Static"
  131. * routes.news.route = "news"
  132. * routes.news.defaults.controller = "news"
  133. * routes.news.defaults.action = "list"
  134. *
  135. * And finally after you have created a Zend_Config with above ini:
  136. * $router = new Zend_Controller_Router_Rewrite();
  137. * $router->addConfig($config, 'routes');
  138. *
  139. * @param Zend_Config $config Configuration object
  140. * @param string $section Name of the config section containing route's definitions
  141. * @throws Zend_Controller_Router_Exception
  142. * @return Zend_Controller_Router_Rewrite
  143. */
  144. public function addConfig(Zend_Config $config, $section = null)
  145. {
  146. if ($section !== null) {
  147. if ($config->{$section} === null) {
  148. require_once 'Zend/Controller/Router/Exception.php';
  149. throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'");
  150. }
  151. $config = $config->{$section};
  152. }
  153. foreach ($config as $name => $info) {
  154. $route = $this->_getRouteFromConfig($info);
  155. if ($route instanceof Zend_Controller_Router_Route_Chain) {
  156. if (!isset($info->chain)) {
  157. require_once 'Zend/Controller/Router/Exception.php';
  158. throw new Zend_Controller_Router_Exception("No chain defined");
  159. }
  160. if ($info->chain instanceof Zend_Config) {
  161. $childRouteNames = $info->chain;
  162. } else {
  163. $childRouteNames = explode(',', $info->chain);
  164. }
  165. foreach ($childRouteNames as $childRouteName) {
  166. $childRoute = $this->getRoute(trim($childRouteName));
  167. $route->chain($childRoute);
  168. }
  169. $this->addRoute($name, $route);
  170. } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) {
  171. $this->_addChainRoutesFromConfig($name, $route, $info->chains);
  172. } else {
  173. $this->addRoute($name, $route);
  174. }
  175. }
  176. return $this;
  177. }
  178. /**
  179. * Get a route frm a config instance
  180. *
  181. * @param Zend_Config $info
  182. * @return Zend_Controller_Router_Route_Interface
  183. */
  184. protected function _getRouteFromConfig(Zend_Config $info)
  185. {
  186. $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route';
  187. if (!class_exists($class)) {
  188. require_once 'Zend/Loader.php';
  189. Zend_Loader::loadClass($class);
  190. }
  191. $route = call_user_func(
  192. array(
  193. $class,
  194. 'getInstance'
  195. ), $info
  196. );
  197. if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) {
  198. $route->isAbstract(true);
  199. }
  200. return $route;
  201. }
  202. /**
  203. * Add chain routes from a config route
  204. *
  205. * @param string $name
  206. * @param Zend_Controller_Router_Route_Interface $route
  207. * @param Zend_Config $childRoutesInfo
  208. * @return void
  209. */
  210. protected function _addChainRoutesFromConfig(
  211. $name,
  212. Zend_Controller_Router_Route_Interface $route,
  213. Zend_Config $childRoutesInfo
  214. )
  215. {
  216. foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) {
  217. if (is_string($childRouteInfo)) {
  218. $childRouteName = $childRouteInfo;
  219. $childRoute = $this->getRoute($childRouteName);
  220. } else {
  221. $childRoute = $this->_getRouteFromConfig($childRouteInfo);
  222. }
  223. if ($route instanceof Zend_Controller_Router_Route_Chain) {
  224. $chainRoute = clone $route;
  225. $chainRoute->chain($childRoute);
  226. } else {
  227. $chainRoute = $route->chain($childRoute);
  228. }
  229. $chainName = $name . $this->_chainNameSeparator . $childRouteName;
  230. if (isset($childRouteInfo->chains)) {
  231. $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains);
  232. } else {
  233. $this->addRoute($chainName, $chainRoute);
  234. }
  235. }
  236. }
  237. /**
  238. * Remove a route from the route chain
  239. *
  240. * @param string $name Name of the route
  241. * @throws Zend_Controller_Router_Exception
  242. * @return Zend_Controller_Router_Rewrite
  243. */
  244. public function removeRoute($name)
  245. {
  246. if (!isset($this->_routes[$name])) {
  247. require_once 'Zend/Controller/Router/Exception.php';
  248. throw new Zend_Controller_Router_Exception("Route $name is not defined");
  249. }
  250. unset($this->_routes[$name]);
  251. return $this;
  252. }
  253. /**
  254. * Remove all standard default routes
  255. *
  256. * @return Zend_Controller_Router_Rewrite
  257. */
  258. public function removeDefaultRoutes()
  259. {
  260. $this->_useDefaultRoutes = false;
  261. return $this;
  262. }
  263. /**
  264. * Check if named route exists
  265. *
  266. * @param string $name Name of the route
  267. * @return boolean
  268. */
  269. public function hasRoute($name)
  270. {
  271. return isset($this->_routes[$name]);
  272. }
  273. /**
  274. * Retrieve a named route
  275. *
  276. * @param string $name Name of the route
  277. * @throws Zend_Controller_Router_Exception
  278. * @return Zend_Controller_Router_Route_Interface Route object
  279. */
  280. public function getRoute($name)
  281. {
  282. if (!isset($this->_routes[$name])) {
  283. require_once 'Zend/Controller/Router/Exception.php';
  284. throw new Zend_Controller_Router_Exception("Route $name is not defined");
  285. }
  286. return $this->_routes[$name];
  287. }
  288. /**
  289. * Retrieve a currently matched route
  290. *
  291. * @throws Zend_Controller_Router_Exception
  292. * @return Zend_Controller_Router_Route_Interface Route object
  293. */
  294. public function getCurrentRoute()
  295. {
  296. if (!isset($this->_currentRoute)) {
  297. require_once 'Zend/Controller/Router/Exception.php';
  298. throw new Zend_Controller_Router_Exception("Current route is not defined");
  299. }
  300. return $this->getRoute($this->_currentRoute);
  301. }
  302. /**
  303. * Retrieve a name of currently matched route
  304. *
  305. * @throws Zend_Controller_Router_Exception
  306. * @return string Route name
  307. */
  308. public function getCurrentRouteName()
  309. {
  310. if (!isset($this->_currentRoute)) {
  311. require_once 'Zend/Controller/Router/Exception.php';
  312. throw new Zend_Controller_Router_Exception("Current route is not defined");
  313. }
  314. return $this->_currentRoute;
  315. }
  316. /**
  317. * Retrieve an array of routes added to the route chain
  318. *
  319. * @return array All of the defined routes
  320. */
  321. public function getRoutes()
  322. {
  323. return $this->_routes;
  324. }
  325. /**
  326. * Find a matching route to the current PATH_INFO and inject
  327. * returning values to the Request object.
  328. *
  329. * @param Zend_Controller_Request_Abstract $request
  330. * @throws Zend_Controller_Router_Exception
  331. * @return Zend_Controller_Request_Abstract Request object
  332. */
  333. public function route(Zend_Controller_Request_Abstract $request)
  334. {
  335. if (!$request instanceof Zend_Controller_Request_Http) {
  336. require_once 'Zend/Controller/Router/Exception.php';
  337. throw new Zend_Controller_Router_Exception(
  338. 'Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'
  339. );
  340. }
  341. if ($this->_useDefaultRoutes) {
  342. $this->addDefaultRoutes();
  343. }
  344. // Find the matching route
  345. $routeMatched = false;
  346. foreach (array_reverse($this->_routes, true) as $name => $route) {
  347. // TODO: Should be an interface method. Hack for 1.0 BC
  348. if (method_exists($route, 'isAbstract') && $route->isAbstract()) {
  349. continue;
  350. }
  351. // TODO: Should be an interface method. Hack for 1.0 BC
  352. if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
  353. $match = $request->getPathInfo();
  354. } else {
  355. $match = $request;
  356. }
  357. if ($params = $route->match($match)) {
  358. $this->_setRequestParams($request, $params);
  359. $this->_currentRoute = $name;
  360. $routeMatched = true;
  361. break;
  362. }
  363. }
  364. if (!$routeMatched) {
  365. require_once 'Zend/Controller/Router/Exception.php';
  366. throw new Zend_Controller_Router_Exception('No route matched the request', 404);
  367. }
  368. if ($this->_useCurrentParamsAsGlobal) {
  369. $params = $request->getParams();
  370. foreach ($params as $param => $value) {
  371. $this->setGlobalParam($param, $value);
  372. }
  373. }
  374. return $request;
  375. }
  376. /**
  377. * Sets parameters for request object
  378. *
  379. * Module name, controller name and action name
  380. *
  381. * @param Zend_Controller_Request_Abstract $request
  382. * @param array $params
  383. */
  384. protected function _setRequestParams($request, $params)
  385. {
  386. foreach ($params as $param => $value) {
  387. $request->setParam($param, $value);
  388. if ($param === $request->getModuleKey()) {
  389. $request->setModuleName($value);
  390. }
  391. if ($param === $request->getControllerKey()) {
  392. $request->setControllerName($value);
  393. }
  394. if ($param === $request->getActionKey()) {
  395. $request->setActionName($value);
  396. }
  397. }
  398. }
  399. /**
  400. * Generates a URL path that can be used in URL creation, redirection, etc.
  401. *
  402. * @param array $userParams Options passed by a user used to override parameters
  403. * @param mixed $name The name of a Route to use
  404. * @param bool $reset Whether to reset to the route defaults ignoring URL params
  405. * @param bool $encode Tells to encode URL parts on output
  406. * @throws Zend_Controller_Router_Exception
  407. * @return string Resulting absolute URL path
  408. */
  409. public function assemble($userParams, $name = null, $reset = false, $encode = true)
  410. {
  411. if (!is_array($userParams)) {
  412. require_once 'Zend/Controller/Router/Exception.php';
  413. throw new Zend_Controller_Router_Exception('userParams must be an array');
  414. }
  415. if ($name == null) {
  416. try {
  417. $name = $this->getCurrentRouteName();
  418. } catch (Zend_Controller_Router_Exception $e) {
  419. $name = 'default';
  420. }
  421. }
  422. // Use UNION (+) in order to preserve numeric keys
  423. $params = $userParams + $this->_globalParams;
  424. $route = $this->getRoute($name);
  425. $url = $route->assemble($params, $reset, $encode);
  426. if (!preg_match('|^[a-z]+://|', $url)) {
  427. $url = rtrim($this->getFrontController()->getBaseUrl(), self::URI_DELIMITER) . self::URI_DELIMITER . $url;
  428. }
  429. return $url;
  430. }
  431. /**
  432. * Set a global parameter
  433. *
  434. * @param string $name
  435. * @param mixed $value
  436. * @return Zend_Controller_Router_Rewrite
  437. */
  438. public function setGlobalParam($name, $value)
  439. {
  440. $this->_globalParams[$name] = $value;
  441. return $this;
  442. }
  443. /**
  444. * Set the separator to use with chain names
  445. *
  446. * @param string $separator The separator to use
  447. * @return Zend_Controller_Router_Rewrite
  448. */
  449. public function setChainNameSeparator($separator)
  450. {
  451. $this->_chainNameSeparator = $separator;
  452. return $this;
  453. }
  454. /**
  455. * Get the separator to use for chain names
  456. *
  457. * @return string
  458. */
  459. public function getChainNameSeparator()
  460. {
  461. return $this->_chainNameSeparator;
  462. }
  463. /**
  464. * Determines/returns whether to use the request parameters as global parameters.
  465. *
  466. * @param boolean|null $use
  467. * Null/unset when you want to retrieve the current state.
  468. * True when request parameters should be global, false otherwise
  469. * @return boolean|Zend_Controller_Router_Rewrite
  470. * Returns a boolean if first param isn't set, returns an
  471. * instance of Zend_Controller_Router_Rewrite otherwise.
  472. *
  473. */
  474. public function useRequestParametersAsGlobal($use = null)
  475. {
  476. if ($use === null) {
  477. return $this->_useCurrentParamsAsGlobal;
  478. }
  479. $this->_useCurrentParamsAsGlobal = (bool)$use;
  480. return $this;
  481. }
  482. }