RouterTest.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\tests\cases\net\http;
  9. use lithium\action\Request;
  10. use lithium\net\http\Route;
  11. use lithium\net\http\Router;
  12. use lithium\action\Response;
  13. class RouterTest extends \lithium\test\Unit {
  14. public $request = null;
  15. protected $_routes = array();
  16. public function setUp() {
  17. $this->request = new Request();
  18. $this->_routes = Router::get();
  19. Router::reset();
  20. }
  21. public function tearDown() {
  22. Router::reset();
  23. foreach ($this->_routes as $route) {
  24. Router::connect($route);
  25. }
  26. }
  27. public function testBasicRouteConnection() {
  28. $result = Router::connect('/hello', array('controller' => 'posts', 'action' => 'index'));
  29. $expected = array(
  30. 'template' => '/hello',
  31. 'pattern' => '@^/hello$@u',
  32. 'params' => array('controller' => 'Posts', 'action' => 'index'),
  33. 'match' => array('controller' => 'Posts', 'action' => 'index'),
  34. 'meta' => array(),
  35. 'persist' => array('controller'),
  36. 'defaults' => array(),
  37. 'keys' => array(),
  38. 'subPatterns' => array(),
  39. 'handler' => null
  40. );
  41. $this->assertEqual($expected, $result->export());
  42. $result = Router::connect('/{:controller}/{:action}', array('action' => 'view'));
  43. $this->assertTrue($result instanceof Route);
  44. $expected = array(
  45. 'template' => '/{:controller}/{:action}',
  46. 'pattern' => '@^(?:/(?P<controller>[^\\/]+))(?:/(?P<action>[^\\/]+)?)?$@u',
  47. 'params' => array('action' => 'view'),
  48. 'defaults' => array('action' => 'view'),
  49. 'match' => array(),
  50. 'meta' => array(),
  51. 'persist' => array('controller'),
  52. 'keys' => array('controller' => 'controller', 'action' => 'action'),
  53. 'subPatterns' => array(),
  54. 'handler' => null
  55. );
  56. $this->assertEqual($expected, $result->export());
  57. }
  58. /**
  59. * Tests generating routes with required parameters which are not present in the URL.
  60. */
  61. public function testConnectingWithRequiredParams() {
  62. $result = Router::connect('/{:controller}/{:action}', array(
  63. 'action' => 'view', 'required' => true
  64. ));
  65. $expected = array(
  66. 'template' => '/{:controller}/{:action}',
  67. 'pattern' => '@^(?:/(?P<controller>[^\\/]+))(?:/(?P<action>[^\\/]+)?)?$@u',
  68. 'keys' => array('controller' => 'controller', 'action' => 'action'),
  69. 'params' => array('action' => 'view', 'required' => true),
  70. 'defaults' => array('action' => 'view'),
  71. 'match' => array('required' => true),
  72. 'meta' => array(),
  73. 'persist' => array('controller'),
  74. 'subPatterns' => array(),
  75. 'handler' => null
  76. );
  77. $this->assertEqual($expected, $result->export());
  78. }
  79. public function testConnectingWithDefaultParams() {
  80. $result = Router::connect('/{:controller}/{:action}', array('action' => 'archive'));
  81. $expected = array(
  82. 'template' => '/{:controller}/{:action}',
  83. 'pattern' => '@^(?:/(?P<controller>[^\/]+))(?:/(?P<action>[^\/]+)?)?$@u',
  84. 'keys' => array('controller' => 'controller', 'action' => 'action'),
  85. 'params' => array('action' => 'archive'),
  86. 'match' => array(),
  87. 'meta' => array(),
  88. 'persist' => array('controller'),
  89. 'defaults' => array('action' => 'archive'),
  90. 'subPatterns' => array(),
  91. 'handler' => null
  92. );
  93. $this->assertEqual($expected, $result->export());
  94. }
  95. /**
  96. * Tests basic options for connecting routes.
  97. */
  98. public function testBasicRouteMatching() {
  99. Router::connect('/hello', array('controller' => 'posts', 'action' => 'index'));
  100. $expected = array('controller' => 'Posts', 'action' => 'index');
  101. foreach (array('/hello/', '/hello', 'hello/', 'hello') as $url) {
  102. $this->request->url = $url;
  103. $result = Router::parse($this->request);
  104. $this->assertEqual($expected, $result->params);
  105. $this->assertEqual(array('controller'), $result->persist);
  106. }
  107. }
  108. public function testRouteMatchingWithDefaultParameters() {
  109. Router::connect('/{:controller}/{:action}', array('action' => 'view'));
  110. $expected = array('controller' => 'posts', 'action' => 'view');
  111. foreach (array('/posts/view', '/posts', 'posts', 'posts/view', 'posts/view/') as $url) {
  112. $this->request->url = $url;
  113. $result = Router::parse($this->request);
  114. $this->assertEqual($expected, $result->params);
  115. $this->assertEqual(array('controller'), $result->persist);
  116. }
  117. $expected['action'] = 'index';
  118. foreach (array('/posts/index', 'posts/index', 'posts/index/') as $url) {
  119. $this->request->url = $url;
  120. $result = Router::parse($this->request);
  121. $this->assertEqual($expected, $result->params);
  122. }
  123. $this->request->url = '/posts/view/1';
  124. $this->assertNull(Router::parse($this->request));
  125. }
  126. /**
  127. * Tests that URLs specified as "Controller::action" are interpreted properly.
  128. */
  129. public function testStringActions() {
  130. Router::connect('/login', array('controller' => 'sessions', 'action' => 'create'));
  131. Router::connect('/{:controller}/{:action}');
  132. $result = Router::match("Sessions::create");
  133. $this->assertEqual('/login', $result);
  134. $result = Router::match("Posts::index");
  135. $this->assertEqual('/posts', $result);
  136. $result = Router::match("ListItems::archive");
  137. $this->assertEqual('/list_items/archive', $result);
  138. }
  139. public function testNamedAnchor() {
  140. Router::connect('/{:controller}/{:action}');
  141. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array('id' => null));
  142. $result = Router::match(array('Posts::edit', '#' => 'foo'));
  143. $this->assertEqual('/posts/edit#foo', $result);
  144. $result = Router::match(array('Posts::edit', 'id' => 42, '#' => 'foo'));
  145. $this->assertEqual('/posts/edit/42#foo', $result);
  146. $result = Router::match(array('controller' => 'users', 'action' => 'view', '#' => 'blah'));
  147. $this->assertEqual('/users/view#blah', $result);
  148. $result = Router::match(array(
  149. 'controller' => 'users', 'action' => 'view', 'id' => 47, '#' => 'blargh'
  150. ));
  151. $this->assertEqual('/users/view/47#blargh', $result);
  152. }
  153. public function testQueryString() {
  154. Router::connect('/{:controller}/{:action}');
  155. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array('id' => null));
  156. $result = Router::match(array('Posts::edit', '?' => array('key' => 'value')));
  157. $this->assertEqual('/posts/edit?key=value', $result);
  158. $result = Router::match(array(
  159. 'Posts::edit', 'id' => 42, '?' => array('key' => 'value', 'test' => 'foo')
  160. ));
  161. $this->assertEqual('/posts/edit/42?key=value&test=foo', $result);
  162. }
  163. /**
  164. * Tests that URLs specified as "Controller::action" and including additional parameters are
  165. * interpreted properly.
  166. */
  167. public function testEmbeddedStringActions() {
  168. Router::connect('/logout/{:id:[0-9]{5,6}}', array(
  169. 'controller' => 'sessions', 'action' => 'destroy', 'id' => null
  170. ));
  171. Router::connect('/{:controller}/{:action}');
  172. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array('id' => null));
  173. $result = Router::match("Sessions::create");
  174. $this->assertEqual('/sessions/create', $result);
  175. $result = Router::match(array("Sessions::create"));
  176. $this->assertEqual('/sessions/create', $result);
  177. $result = Router::match(array("Sessions::destroy", 'id' => '03815'));
  178. $this->assertEqual('/logout/03815', $result);
  179. $result = Router::match("Posts::index");
  180. $this->assertEqual('/posts', $result);
  181. $ex = "No parameter match found for URL ";
  182. $ex .= "`('controller' => 'Sessions', 'action' => 'create', 'id' => 'foo')`.";
  183. $this->expectException($ex);
  184. $result = Router::match(array("Sessions::create", 'id' => 'foo'));
  185. }
  186. /**
  187. * Tests that routes can be created with shorthand strings, i.e. `'Controller::action'` and
  188. * `array('Controller::action', 'id' => '...')`.
  189. */
  190. public function testStringParameterConnect() {
  191. Router::connect('/posts/{:id:[0-9a-f]{24}}', 'Posts::edit');
  192. $result = Router::match(array(
  193. 'controller' => 'posts', 'action' => 'edit', 'id' => '4bbf25bd8ead0e5180130000'
  194. ));
  195. $expected = '/posts/4bbf25bd8ead0e5180130000';
  196. $this->assertEqual($expected, $result);
  197. $ex = "No parameter match found for URL `(";
  198. $ex .= "'controller' => 'Posts', 'action' => 'view', 'id' => '4bbf25bd8ead0e5180130000')`.";
  199. $this->expectException($ex);
  200. $result = Router::match(array(
  201. 'controller' => 'posts', 'action' => 'view', 'id' => '4bbf25bd8ead0e5180130000'
  202. ));
  203. $this->assertFalse(ob_get_length());
  204. }
  205. public function testShorthandParameterMatching() {
  206. Router::reset();
  207. Router::connect('/posts/{:page:[0-9]+}', array('Posts::index', 'page' => '1'));
  208. $result = Router::match(array('controller' => 'posts', 'page' => '5'));
  209. $expected = '/posts/5';
  210. $this->assertEqual($expected, $result);
  211. $result = Router::match(array('Posts::index', 'page' => '10'));
  212. $expected = '/posts/10';
  213. $this->assertEqual($expected, $result);
  214. $request = new Request(array('url' => '/posts/13'));
  215. $result = Router::process($request);
  216. $expected = array('controller' => 'Posts', 'action' => 'index', 'page' => '13');
  217. $this->assertEqual($expected, $result->params);
  218. }
  219. /**
  220. * Tests that routing is fully reset when calling `Router::reset()`.
  221. */
  222. public function testResettingRoutes() {
  223. Router::connect('/{:controller}', array('controller' => 'posts'));
  224. $this->request->url = '/hello';
  225. $expected = array('controller' => 'hello', 'action' => 'index');
  226. $result = Router::parse($this->request);
  227. $this->assertEqual($expected, $result->params);
  228. Router::reset();
  229. $this->assertNull(Router::parse($this->request));
  230. }
  231. /**
  232. * Tests matching routes where the route template is a static string with no insert parameters.
  233. */
  234. public function testRouteMatchingWithNoInserts() {
  235. Router::connect('/login', array('controller' => 'sessions', 'action' => 'add'));
  236. $result = Router::match(array('controller' => 'sessions', 'action' => 'add'));
  237. $this->assertEqual('/login', $result);
  238. $this->expectException(
  239. "No parameter match found for URL `('controller' => 'Sessions', 'action' => 'index')`."
  240. );
  241. Router::match(array('controller' => 'sessions', 'action' => 'index'));
  242. }
  243. /**
  244. * Test matching routes with only insert parameters and no default values.
  245. */
  246. public function testRouteMatchingWithOnlyInserts() {
  247. Router::connect('/{:controller}');
  248. $this->assertEqual('/posts', Router::match(array('controller' => 'posts')));
  249. $this->expectException(
  250. "No parameter match found for URL `('controller' => 'Posts', 'action' => 'view')`."
  251. );
  252. Router::match(array('controller' => 'posts', 'action' => 'view'));
  253. }
  254. /**
  255. * Test matching routes with insert parameters which have default values.
  256. */
  257. public function testRouteMatchingWithInsertsAndDefaults() {
  258. Router::connect('/{:controller}/{:action}', array('action' => 'archive'));
  259. $this->assertEqual('/posts/index', Router::match(array('controller' => 'posts')));
  260. $result = Router::match(array('controller' => 'posts', 'action' => 'archive'));
  261. $this->assertEqual('/posts', $result);
  262. Router::reset();
  263. Router::connect('/{:controller}/{:action}', array('controller' => 'users'));
  264. $result = Router::match(array('action' => 'view'));
  265. $this->assertEqual('/users/view', $result);
  266. $result = Router::match(array('controller' => 'posts', 'action' => 'view'));
  267. $this->assertEqual('/posts/view', $result);
  268. $ex = "No parameter match found for URL ";
  269. $ex .= "`('controller' => 'Posts', 'action' => 'view', 'id' => '2')`.";
  270. $this->expectException($ex);
  271. Router::match(array('controller' => 'posts', 'action' => 'view', 'id' => '2'));
  272. }
  273. /**
  274. * Tests matching routes and returning an absolute (protocol + hostname) URL.
  275. */
  276. public function testRouteMatchAbsoluteUrl() {
  277. Router::connect('/login', array('controller' => 'sessions', 'action' => 'add'));
  278. $result = Router::match('Sessions::add', $this->request);
  279. $base = $this->request->env('base');
  280. $this->assertEqual($base . '/login', $result);
  281. $result = Router::match('Sessions::add', $this->request, array('absolute' => true));
  282. $base = $this->request->env('HTTPS') ? 'https://' : 'http://';
  283. $base .= $this->request->env('HTTP_HOST');
  284. $base .= $this->request->env('base');
  285. $this->assertEqual($base . '/login', $result);
  286. $result = Router::match('Sessions::add',
  287. $this->request, array('host' => 'test.local', 'absolute' => true)
  288. );
  289. $base = $this->request->env('HTTPS') ? 'https://' : 'http://';
  290. $base .= 'test.local';
  291. $base .= $this->request->env('base');
  292. $this->assertEqual($base . '/login', $result);
  293. $result = Router::match('Sessions::add',
  294. $this->request, array('scheme' => 'https://', 'absolute' => true)
  295. );
  296. $base = 'https://' . $this->request->env('HTTP_HOST');
  297. $base .= $this->request->env('base');
  298. $this->assertEqual($base . '/login', $result);
  299. $result = Router::match('Sessions::add',
  300. $this->request, array('scheme' => 'https://', 'absolute' => true)
  301. );
  302. $base = 'https://' . $this->request->env('HTTP_HOST');
  303. $base .= $this->request->env('base');
  304. $this->assertEqual($base . '/login', $result);
  305. }
  306. /**
  307. * Tests getting routes using `Router::get()`, and checking to see if the routes returned match
  308. * the routes connected.
  309. */
  310. public function testRouteRetrieval() {
  311. $expected = Router::connect('/hello', array('controller' => 'posts', 'action' => 'index'));
  312. $result = Router::get(0);
  313. $this->assertIdentical($expected, $result);
  314. list($result) = Router::get();
  315. $this->assertIdentical($expected, $result);
  316. }
  317. public function testStringUrlGeneration() {
  318. $result = Router::match('/posts');
  319. $expected = '/posts';
  320. $this->assertEqual($expected, $result);
  321. $result = Router::match('/posts');
  322. $this->assertEqual($expected, $result);
  323. $result = Router::match('/posts/view/5');
  324. $expected = '/posts/view/5';
  325. $this->assertEqual($expected, $result);
  326. $request = new Request(array('base' => '/my/web/path'));
  327. $result = Router::match('/posts', $request);
  328. $expected = '/my/web/path/posts';
  329. $this->assertEqual($expected, $result);
  330. $request = new Request(array('base' => '/my/web/path'));
  331. $result = Router::match('/some/where', $request, array('absolute' => true));
  332. $prefix = $this->request->env('HTTPS') ? 'https://' : 'http://';
  333. $prefix .= $this->request->env('HTTP_HOST');
  334. $this->assertEqual($prefix . '/my/web/path/some/where', $result);
  335. $result = Router::match('mailto:foo@localhost');
  336. $expected = 'mailto:foo@localhost';
  337. $this->assertEqual($expected, $result);
  338. $result = Router::match('#top');
  339. $expected = '#top';
  340. $this->assertEqual($expected, $result);
  341. }
  342. public function testWithWildcardString() {
  343. Router::connect('/add/{:args}', array('controller' => 'tests', 'action' => 'add'));
  344. $expected = '/add';
  345. $result = Router::match('/add');
  346. $this->assertEqual($expected, $result);
  347. $expected = '/add/alke';
  348. $result = Router::match('/add/alke');
  349. $this->assertEqual($expected, $result);
  350. }
  351. public function testWithWildcardArray() {
  352. Router::connect('/add/{:args}', array('controller' => 'tests', 'action' => 'add'));
  353. $expected = '/add';
  354. $result = Router::match(array('controller' => 'tests', 'action' => 'add'));
  355. $this->assertEqual($expected, $result);
  356. $expected = '/add/alke';
  357. $result = Router::match(array(
  358. 'controller' => 'tests', 'action' => 'add', 'args' => array('alke')
  359. ));
  360. $this->assertEqual($expected, $result);
  361. $expected = '/add/alke/php';
  362. $result = Router::match(array(
  363. 'controller' => 'tests', 'action' => 'add', 'args' => array('alke', 'php')
  364. ));
  365. $this->assertEqual($expected, $result);
  366. }
  367. public function testProcess() {
  368. Router::connect('/add/{:args}', array('controller' => 'tests', 'action' => 'add'));
  369. $request = Router::process(new Request(array('url' => '/add/foo/bar')));
  370. $params = array('controller' => 'Tests', 'action' => 'add', 'args' => array('foo', 'bar'));
  371. $this->assertEqual($params, $request->params);
  372. $this->assertEqual(array('controller'), $request->persist);
  373. $request = Router::process(new Request(array('url' => '/remove/foo/bar')));
  374. $this->assertFalse($request->params);
  375. }
  376. /**
  377. * Tests that the order of the parameters is respected so it can trim
  378. * the URL correctly.
  379. */
  380. public function testParameterOrderIsRespected() {
  381. Router::connect('/{:locale}/{:controller}/{:action}/{:args}');
  382. Router::connect('/{:controller}/{:action}/{:args}');
  383. $request = Router::process(new Request(array('url' => 'posts')));
  384. $url = Router::match('Posts::index', $request);
  385. $this->assertEqual($this->request->env('base') . '/posts', $url);
  386. $request = Router::process(new Request(array('url' => 'fr/posts')));
  387. $params = array('Posts::index', 'locale' => 'fr');
  388. $url = Router::match($params, $request);
  389. $this->assertEqual($this->request->env('base') . '/fr/posts', $url);
  390. }
  391. /**
  392. * Tests that a request context with persistent parameters generates URLs where those parameters
  393. * are properly taken into account.
  394. */
  395. public function testParameterPersistence() {
  396. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array(), array(
  397. 'persist' => array('controller', 'id')
  398. ));
  399. // URLs generated with $request will now have the 'controller' and 'id'
  400. // parameters copied to new URLs.
  401. $request = Router::process(new Request(array('url' => 'posts/view/1138')));
  402. $params = array('action' => 'edit');
  403. $url = Router::match($params, $request); // Returns: '/posts/edit/1138'
  404. $this->assertEqual($this->request->env('base') . '/posts/edit/1138', $url);
  405. Router::connect(
  406. '/add/{:args}',
  407. array('controller' => 'tests', 'action' => 'add'),
  408. array('persist' => array('controller', 'action'))
  409. );
  410. $request = Router::process(new Request(array('url' => '/add/foo/bar', 'base' => '')));
  411. $path = Router::match(array('args' => array('baz', 'dib')), $request);
  412. $this->assertEqual('/add/baz/dib', $path);
  413. }
  414. /**
  415. * Tests that persistent parameters can be overridden with nulled-out values.
  416. */
  417. public function testOverridingPersistentParameters() {
  418. Router::connect(
  419. '/admin/{:controller}/{:action}',
  420. array('admin' => true),
  421. array('persist' => array('admin', 'controller'))
  422. );
  423. Router::connect('/{:controller}/{:action}');
  424. $request = Router::process(new Request(array('url' => '/admin/posts/add', 'base' => '')));
  425. $expected = array('controller' => 'posts', 'action' => 'add', 'admin' => true);
  426. $this->assertEqual($expected, $request->params);
  427. $this->assertEqual(array('admin', 'controller'), $request->persist);
  428. $url = Router::match(array('action' => 'archive'), $request);
  429. $this->assertEqual('/admin/posts/archive', $url);
  430. $url = Router::match(array('action' => 'archive', 'admin' => null), $request);
  431. $this->assertEqual('/posts/archive', $url);
  432. }
  433. /**
  434. * Tests passing a closure handler to `Router::connect()` to bypass or augment default
  435. * dispatching.
  436. */
  437. public function testRouteHandler() {
  438. Router::connect('/login', 'Users::login');
  439. Router::connect('/users/login', array(), function($request) {
  440. return new Response(array(
  441. 'location' => array('controller' => 'users', 'action' => 'login')
  442. ));
  443. });
  444. $result = Router::process(new Request(array('url' => '/users/login')));
  445. $this->assertTrue($result instanceof Response);
  446. $headers = array('Location' => '/login');
  447. $this->assertEqual($headers, $result->headers);
  448. }
  449. /**
  450. * Tests that a successful match against a route with template `'/'` operating at the root of
  451. * a domain never returns an empty string.
  452. */
  453. public function testMatchingEmptyRoute() {
  454. Router::connect('/', 'Users::view');
  455. $request = new Request(array('base' => '/'));
  456. $url = Router::match(array('controller' => 'users', 'action' => 'view'), $request);
  457. $this->assertEqual('/', $url);
  458. $request = new Request(array('base' => ''));
  459. $url = Router::match(array('controller' => 'users', 'action' => 'view'), $request);
  460. $this->assertEqual('/', $url);
  461. }
  462. /**
  463. * Tests routing based on content type extensions, with HTML being the default when types are
  464. * not defined.
  465. */
  466. public function testTypeBasedRouting() {
  467. Router::connect('/{:controller}/{:id:[0-9]+}', array(
  468. 'action' => 'index', 'type' => 'html', 'id' => null
  469. ));
  470. Router::connect('/{:controller}/{:id:[0-9]+}.{:type}', array(
  471. 'action' => 'index', 'id' => null
  472. ));
  473. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array(
  474. 'type' => 'html', 'id' => null
  475. ));
  476. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}.{:type}', array('id' => null));
  477. $url = Router::match(array('controller' => 'posts', 'type' => 'html'));
  478. $this->assertEqual('/posts', $url);
  479. $url = Router::match(array('controller' => 'posts', 'type' => 'json'));
  480. $this->assertEqual('/posts.json', $url);
  481. }
  482. /**
  483. * Tests that routes can be connected and correctly match based on HTTP headers or method verbs.
  484. */
  485. public function testHttpMethodBasedRouting() {
  486. Router::connect('/{:controller}/{:id:[0-9]+}', array(
  487. 'http:method' => 'GET', 'action' => 'view'
  488. ));
  489. Router::connect('/{:controller}/{:id:[0-9]+}', array(
  490. 'http:method' => 'PUT', 'action' => 'edit'
  491. ));
  492. $request = new Request(array('url' => '/posts/13', 'env' => array(
  493. 'REQUEST_METHOD' => 'GET'
  494. )));
  495. $params = Router::process($request)->params;
  496. $expected = array('controller' => 'posts', 'action' => 'view', 'id' => '13');
  497. $this->assertEqual($expected, $params);
  498. $this->assertEqual('/posts/13', Router::match($params));
  499. $request = new Request(array('url' => '/posts/13', 'env' => array(
  500. 'REQUEST_METHOD' => 'PUT'
  501. )));
  502. $params = Router::process($request)->params;
  503. $expected = array('controller' => 'posts', 'action' => 'edit', 'id' => '13');
  504. $this->assertEqual($expected, $params);
  505. $request = new Request(array('url' => '/posts/13', 'env' => array(
  506. 'REQUEST_METHOD' => 'POST'
  507. )));
  508. $params = Router::process($request)->params;
  509. $this->assertFalse($params);
  510. }
  511. /**
  512. * Tests that the class dependency configuration can be modified.
  513. */
  514. public function testCustomConfiguration() {
  515. $old = Router::config();
  516. $config = array('classes' => array('route' => 'my\custom\Route'), 'unicode' => true);
  517. Router::config($config);
  518. $this->assertEqual($config, Router::config());
  519. Router::config($old);
  520. $this->assertEqual($old, Router::config());
  521. }
  522. /**
  523. * Tests that continuation routes properly fall through and aggregate multiple route parameters.
  524. */
  525. public function testRouteContinuations() {
  526. Router::connect('/{:locale:en|de|it|jp}/{:args}', array(), array('continue' => true));
  527. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}');
  528. $request = new Request(array('url' => '/en/posts/view/1138'));
  529. $result = Router::process($request)->params;
  530. $expected = array (
  531. 'controller' => 'posts', 'action' => 'view', 'id' => '1138', 'locale' => 'en'
  532. );
  533. $this->assertEqual($expected, $result);
  534. $request = new Request(array('url' => '/en/foo/bar/baz'));
  535. $this->assertNull(Router::parse($request));
  536. Router::reset();
  537. Router::connect('/{:args}/{:locale:en|de|it|jp}', array(), array('continue' => true));
  538. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}');
  539. $request = new Request(array('url' => '/posts/view/1138/en'));
  540. $result = Router::process($request)->params;
  541. $this->assertEqual($expected, $result);
  542. Router::reset();
  543. Router::connect('/{:locale:en|de|it|jp}/{:args}', array(), array('continue' => true));
  544. Router::connect('/', 'Pages::view');
  545. $request = new Request(array('url' => '/en'));
  546. $result = Router::process($request)->params;
  547. $expected = array('locale' => 'en', 'controller' => 'Pages', 'action' => 'view');
  548. $this->assertEqual($expected, $result);
  549. }
  550. /**
  551. * Tests that URLs are properly generated with route continuations.
  552. */
  553. public function testReversingContinuations() {
  554. Router::connect('/{:locale:en|de|it|jp}/{:args}', array(), array('continue' => true));
  555. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}');
  556. Router::connect('/{:controller}/{:action}/{:args}');
  557. $result = Router::match(array('Posts::view', 'id' => 5, 'locale' => 'de'));
  558. $this->assertEqual($result, '/de/posts/view/5');
  559. $result = Router::match(array('Posts::index', 'locale' => 'en', '?' => array('page' => 2)));
  560. $this->assertEqual('/en/posts?page=2', $result);
  561. Router::reset();
  562. Router::connect('/{:locale:en|de|it|jp}/{:args}', array(), array('continue' => true));
  563. Router::connect('/pages/{:args}', 'Pages::view');
  564. $result = Router::match(array('Pages::view', 'locale' => 'en', 'args' => array('about')));
  565. $this->assertEqual('/en/pages/about', $result);
  566. Router::reset();
  567. Router::connect('/admin/{:args}', array('admin' => true), array('continue' => true));
  568. Router::connect('/login', 'Users::login');
  569. $result = Router::match(array('Users::login', 'admin' => true));
  570. $this->assertEqual('/admin/login', $result);
  571. }
  572. /**
  573. * Tests that multiple continuation routes can be applied to the same URL.
  574. */
  575. public function testStackedContinuationRoutes() {
  576. Router::connect('/admin/{:args}', array('admin' => true), array('continue' => true));
  577. Router::connect('/{:locale:en|de|it|jp}/{:args}', array(), array('continue' => true));
  578. Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array('id' => null));
  579. $request = new Request(array('url' => '/en/foo/bar/5'));
  580. $expected = array('controller' => 'foo', 'action' => 'bar', 'id' => '5', 'locale' => 'en');
  581. $this->assertEqual($expected, Router::process($request)->params);
  582. $request = new Request(array('url' => '/admin/foo/bar/5'));
  583. $expected = array('controller' => 'foo', 'action' => 'bar', 'id' => '5', 'admin' => true);
  584. $this->assertEqual($expected, Router::process($request)->params);
  585. $request = new Request(array('url' => '/admin/de/foo/bar/5'));
  586. $expected = array(
  587. 'controller' => 'foo', 'action' => 'bar', 'id' => '5', 'locale' => 'de', 'admin' => true
  588. );
  589. $this->assertEqual($expected, Router::process($request)->params);
  590. $request = new Request(array('url' => '/en/admin/foo/bar/5'));
  591. $this->assertFalse(Router::process($request)->params);
  592. $result = Router::match(array('Foo::bar', 'id' => 5));
  593. $this->assertEqual('/foo/bar/5', $result);
  594. $result = Router::match(array('Foo::bar', 'id' => 5, 'admin' => true));
  595. $this->assertEqual('/admin/foo/bar/5', $result);
  596. $result = Router::match(array('Foo::bar', 'id' => 5, 'admin' => true, 'locale' => 'jp'));
  597. $this->assertEqual('/admin/jp/foo/bar/5', $result);
  598. }
  599. /**
  600. * Tests that continuations can be used for route suffixes.
  601. */
  602. public function testSuffixContinuation() {
  603. Router::connect("/{:args}.{:type}", array(), array('continue' => true));
  604. Router::connect('/{:controller}/{:id:[0-9]+}', array('action' => 'view'));
  605. $result = Router::match(array(
  606. 'controller' => 'versions',
  607. 'action' => 'view',
  608. 'id' => 13,
  609. 'type' => 'jsonp'
  610. ));
  611. $this->assertEqual('/versions/13.jsonp', $result);
  612. $result = Router::match(array(
  613. 'controller' => 'versions',
  614. 'action' => 'view',
  615. 'id' => 13
  616. ));
  617. $this->assertEqual('/versions/13', $result);
  618. }
  619. /**
  620. * Tests default route formatters, and setting/getting new formatters.
  621. */
  622. public function testRouteFormatters() {
  623. $formatters = Router::formatters();
  624. $this->assertEqual(array('args', 'controller'), array_keys($formatters));
  625. $this->assertEqual('foo/bar', $formatters['args'](array('foo', 'bar')));
  626. $this->assertEqual('list_items', $formatters['controller']('ListItems'));
  627. Router::formatters(array('action' => function($value) { return strtolower($value); }));
  628. $formatters = Router::formatters();
  629. $this->assertEqual(array('action', 'args', 'controller'), array_keys($formatters));
  630. Router::formatters(array('action' => null));
  631. $formatters = Router::formatters();
  632. $this->assertEqual(array('args', 'controller'), array_keys($formatters));
  633. }
  634. }
  635. ?>