PhpMatcherDumperTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing\Tests\Matcher\Dumper;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
  13. use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
  14. use Symfony\Component\Routing\Matcher\UrlMatcher;
  15. use Symfony\Component\Routing\RequestContext;
  16. use Symfony\Component\Routing\Route;
  17. use Symfony\Component\Routing\RouteCollection;
  18. class PhpMatcherDumperTest extends TestCase
  19. {
  20. /**
  21. * @var string
  22. */
  23. private $matcherClass;
  24. /**
  25. * @var string
  26. */
  27. private $dumpPath;
  28. protected function setUp()
  29. {
  30. parent::setUp();
  31. $this->matcherClass = uniqid('ProjectUrlMatcher');
  32. $this->dumpPath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_matcher.'.$this->matcherClass.'.php';
  33. }
  34. protected function tearDown()
  35. {
  36. parent::tearDown();
  37. @unlink($this->dumpPath);
  38. }
  39. /**
  40. * @expectedException \LogicException
  41. */
  42. public function testDumpWhenSchemeIsUsedWithoutAProperDumper()
  43. {
  44. $collection = new RouteCollection();
  45. $collection->add('secure', new Route(
  46. '/secure',
  47. [],
  48. [],
  49. [],
  50. '',
  51. ['https']
  52. ));
  53. $dumper = new PhpMatcherDumper($collection);
  54. $dumper->dump();
  55. }
  56. public function testRedirectPreservesUrlEncoding()
  57. {
  58. $collection = new RouteCollection();
  59. $collection->add('foo', new Route('/foo:bar/'));
  60. $class = $this->generateDumpedMatcher($collection, true);
  61. $matcher = $this->getMockBuilder($class)
  62. ->setMethods(['redirect'])
  63. ->setConstructorArgs([new RequestContext()])
  64. ->getMock();
  65. $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/', 'foo')->willReturn([]);
  66. $matcher->match('/foo%3Abar');
  67. }
  68. /**
  69. * @dataProvider getRouteCollections
  70. */
  71. public function testDump(RouteCollection $collection, $fixture, $options = [])
  72. {
  73. $basePath = __DIR__.'/../../Fixtures/dumper/';
  74. $dumper = new PhpMatcherDumper($collection);
  75. $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.');
  76. }
  77. public function getRouteCollections()
  78. {
  79. /* test case 1 */
  80. $collection = new RouteCollection();
  81. $collection->add('overridden', new Route('/overridden'));
  82. // defaults and requirements
  83. $collection->add('foo', new Route(
  84. '/foo/{bar}',
  85. ['def' => 'test'],
  86. ['bar' => 'baz|symfony']
  87. ));
  88. // method requirement
  89. $collection->add('bar', new Route(
  90. '/bar/{foo}',
  91. [],
  92. [],
  93. [],
  94. '',
  95. [],
  96. ['GET', 'head']
  97. ));
  98. // GET method requirement automatically adds HEAD as valid
  99. $collection->add('barhead', new Route(
  100. '/barhead/{foo}',
  101. [],
  102. [],
  103. [],
  104. '',
  105. [],
  106. ['GET']
  107. ));
  108. // simple
  109. $collection->add('baz', new Route(
  110. '/test/baz'
  111. ));
  112. // simple with extension
  113. $collection->add('baz2', new Route(
  114. '/test/baz.html'
  115. ));
  116. // trailing slash
  117. $collection->add('baz3', new Route(
  118. '/test/baz3/'
  119. ));
  120. // trailing slash with variable
  121. $collection->add('baz4', new Route(
  122. '/test/{foo}/'
  123. ));
  124. // trailing slash and method
  125. $collection->add('baz5', new Route(
  126. '/test/{foo}/',
  127. [],
  128. [],
  129. [],
  130. '',
  131. [],
  132. ['post']
  133. ));
  134. // complex name
  135. $collection->add('baz.baz6', new Route(
  136. '/test/{foo}/',
  137. [],
  138. [],
  139. [],
  140. '',
  141. [],
  142. ['put']
  143. ));
  144. // defaults without variable
  145. $collection->add('foofoo', new Route(
  146. '/foofoo',
  147. ['def' => 'test']
  148. ));
  149. // pattern with quotes
  150. $collection->add('quoter', new Route(
  151. '/{quoter}',
  152. [],
  153. ['quoter' => '[\']+']
  154. ));
  155. // space in pattern
  156. $collection->add('space', new Route(
  157. '/spa ce'
  158. ));
  159. // prefixes
  160. $collection1 = new RouteCollection();
  161. $collection1->add('overridden', new Route('/overridden1'));
  162. $collection1->add('foo1', new Route('/{foo}'));
  163. $collection1->add('bar1', new Route('/{bar}'));
  164. $collection1->addPrefix('/b\'b');
  165. $collection2 = new RouteCollection();
  166. $collection2->addCollection($collection1);
  167. $collection2->add('overridden', new Route('/{var}', [], ['var' => '.*']));
  168. $collection1 = new RouteCollection();
  169. $collection1->add('foo2', new Route('/{foo1}'));
  170. $collection1->add('bar2', new Route('/{bar1}'));
  171. $collection1->addPrefix('/b\'b');
  172. $collection2->addCollection($collection1);
  173. $collection2->addPrefix('/a');
  174. $collection->addCollection($collection2);
  175. // overridden through addCollection() and multiple sub-collections with no own prefix
  176. $collection1 = new RouteCollection();
  177. $collection1->add('overridden2', new Route('/old'));
  178. $collection1->add('helloWorld', new Route('/hello/{who}', ['who' => 'World!']));
  179. $collection2 = new RouteCollection();
  180. $collection3 = new RouteCollection();
  181. $collection3->add('overridden2', new Route('/new'));
  182. $collection3->add('hey', new Route('/hey/'));
  183. $collection2->addCollection($collection3);
  184. $collection1->addCollection($collection2);
  185. $collection1->addPrefix('/multi');
  186. $collection->addCollection($collection1);
  187. // "dynamic" prefix
  188. $collection1 = new RouteCollection();
  189. $collection1->add('foo3', new Route('/{foo}'));
  190. $collection1->add('bar3', new Route('/{bar}'));
  191. $collection1->addPrefix('/b');
  192. $collection1->addPrefix('{_locale}');
  193. $collection->addCollection($collection1);
  194. // route between collections
  195. $collection->add('ababa', new Route('/ababa'));
  196. // collection with static prefix but only one route
  197. $collection1 = new RouteCollection();
  198. $collection1->add('foo4', new Route('/{foo}'));
  199. $collection1->addPrefix('/aba');
  200. $collection->addCollection($collection1);
  201. // prefix and host
  202. $collection1 = new RouteCollection();
  203. $route1 = new Route('/route1', [], [], [], 'a.example.com');
  204. $collection1->add('route1', $route1);
  205. $route2 = new Route('/c2/route2', [], [], [], 'a.example.com');
  206. $collection1->add('route2', $route2);
  207. $route3 = new Route('/c2/route3', [], [], [], 'b.example.com');
  208. $collection1->add('route3', $route3);
  209. $route4 = new Route('/route4', [], [], [], 'a.example.com');
  210. $collection1->add('route4', $route4);
  211. $route5 = new Route('/route5', [], [], [], 'c.example.com');
  212. $collection1->add('route5', $route5);
  213. $route6 = new Route('/route6', [], [], [], null);
  214. $collection1->add('route6', $route6);
  215. $collection->addCollection($collection1);
  216. // host and variables
  217. $collection1 = new RouteCollection();
  218. $route11 = new Route('/route11', [], [], [], '{var1}.example.com');
  219. $collection1->add('route11', $route11);
  220. $route12 = new Route('/route12', ['var1' => 'val'], [], [], '{var1}.example.com');
  221. $collection1->add('route12', $route12);
  222. $route13 = new Route('/route13/{name}', [], [], [], '{var1}.example.com');
  223. $collection1->add('route13', $route13);
  224. $route14 = new Route('/route14/{name}', ['var1' => 'val'], [], [], '{var1}.example.com');
  225. $collection1->add('route14', $route14);
  226. $route15 = new Route('/route15/{name}', [], [], [], 'c.example.com');
  227. $collection1->add('route15', $route15);
  228. $route16 = new Route('/route16/{name}', ['var1' => 'val'], [], [], null);
  229. $collection1->add('route16', $route16);
  230. $route17 = new Route('/route17', [], [], [], null);
  231. $collection1->add('route17', $route17);
  232. $collection->addCollection($collection1);
  233. // multiple sub-collections with a single route and a prefix each
  234. $collection1 = new RouteCollection();
  235. $collection1->add('a', new Route('/a...'));
  236. $collection2 = new RouteCollection();
  237. $collection2->add('b', new Route('/{var}'));
  238. $collection3 = new RouteCollection();
  239. $collection3->add('c', new Route('/{var}'));
  240. $collection3->addPrefix('/c');
  241. $collection2->addCollection($collection3);
  242. $collection2->addPrefix('/b');
  243. $collection1->addCollection($collection2);
  244. $collection1->addPrefix('/a');
  245. $collection->addCollection($collection1);
  246. /* test case 2 */
  247. $redirectCollection = clone $collection;
  248. // force HTTPS redirection
  249. $redirectCollection->add('secure', new Route(
  250. '/secure',
  251. [],
  252. [],
  253. [],
  254. '',
  255. ['https']
  256. ));
  257. // force HTTP redirection
  258. $redirectCollection->add('nonsecure', new Route(
  259. '/nonsecure',
  260. [],
  261. [],
  262. [],
  263. '',
  264. ['http']
  265. ));
  266. /* test case 3 */
  267. $rootprefixCollection = new RouteCollection();
  268. $rootprefixCollection->add('static', new Route('/test'));
  269. $rootprefixCollection->add('dynamic', new Route('/{var}'));
  270. $rootprefixCollection->addPrefix('rootprefix');
  271. $route = new Route('/with-condition');
  272. $route->setCondition('context.getMethod() == "GET"');
  273. $rootprefixCollection->add('with-condition', $route);
  274. /* test case 4 */
  275. $headMatchCasesCollection = new RouteCollection();
  276. $headMatchCasesCollection->add('just_head', new Route(
  277. '/just_head',
  278. [],
  279. [],
  280. [],
  281. '',
  282. [],
  283. ['HEAD']
  284. ));
  285. $headMatchCasesCollection->add('head_and_get', new Route(
  286. '/head_and_get',
  287. [],
  288. [],
  289. [],
  290. '',
  291. [],
  292. ['HEAD', 'GET']
  293. ));
  294. $headMatchCasesCollection->add('get_and_head', new Route(
  295. '/get_and_head',
  296. [],
  297. [],
  298. [],
  299. '',
  300. [],
  301. ['GET', 'HEAD']
  302. ));
  303. $headMatchCasesCollection->add('post_and_head', new Route(
  304. '/post_and_head',
  305. [],
  306. [],
  307. [],
  308. '',
  309. [],
  310. ['POST', 'HEAD']
  311. ));
  312. $headMatchCasesCollection->add('put_and_post', new Route(
  313. '/put_and_post',
  314. [],
  315. [],
  316. [],
  317. '',
  318. [],
  319. ['PUT', 'POST']
  320. ));
  321. $headMatchCasesCollection->add('put_and_get_and_head', new Route(
  322. '/put_and_post',
  323. [],
  324. [],
  325. [],
  326. '',
  327. [],
  328. ['PUT', 'GET', 'HEAD']
  329. ));
  330. /* test case 5 */
  331. $groupOptimisedCollection = new RouteCollection();
  332. $groupOptimisedCollection->add('a_first', new Route('/a/11'));
  333. $groupOptimisedCollection->add('a_second', new Route('/a/22'));
  334. $groupOptimisedCollection->add('a_third', new Route('/a/333'));
  335. $groupOptimisedCollection->add('a_wildcard', new Route('/{param}'));
  336. $groupOptimisedCollection->add('a_fourth', new Route('/a/44/'));
  337. $groupOptimisedCollection->add('a_fifth', new Route('/a/55/'));
  338. $groupOptimisedCollection->add('a_sixth', new Route('/a/66/'));
  339. $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}'));
  340. $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/'));
  341. $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/'));
  342. $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/'));
  343. $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/'));
  344. $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/'));
  345. $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/'));
  346. $trailingSlashCollection = new RouteCollection();
  347. $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', [], [], [], '', [], []));
  348. $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', [], [], [], '', [], ['GET']));
  349. $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', [], [], [], '', [], ['HEAD']));
  350. $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', [], [], [], '', [], ['POST']));
  351. $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', [], [], [], '', [], []));
  352. $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', [], [], [], '', [], ['GET']));
  353. $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', [], [], [], '', [], ['HEAD']));
  354. $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', [], [], [], '', [], ['POST']));
  355. $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', [], [], [], '', [], []));
  356. $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', [], [], [], '', [], ['GET']));
  357. $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', [], [], [], '', [], ['HEAD']));
  358. $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', [], [], [], '', [], ['POST']));
  359. $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', [], [], [], '', [], []));
  360. $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', [], [], [], '', [], ['GET']));
  361. $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', [], [], [], '', [], ['HEAD']));
  362. $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', [], [], [], '', [], ['POST']));
  363. return [
  364. [new RouteCollection(), 'url_matcher0.php', []],
  365. [$collection, 'url_matcher1.php', []],
  366. [$redirectCollection, 'url_matcher2.php', ['base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher']],
  367. [$rootprefixCollection, 'url_matcher3.php', []],
  368. [$headMatchCasesCollection, 'url_matcher4.php', []],
  369. [$groupOptimisedCollection, 'url_matcher5.php', ['base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher']],
  370. [$trailingSlashCollection, 'url_matcher6.php', []],
  371. [$trailingSlashCollection, 'url_matcher7.php', ['base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher']],
  372. ];
  373. }
  374. private function generateDumpedMatcher(RouteCollection $collection, $redirectableStub = false)
  375. {
  376. $options = ['class' => $this->matcherClass];
  377. if ($redirectableStub) {
  378. $options['base_class'] = '\Symfony\Component\Routing\Tests\Matcher\Dumper\RedirectableUrlMatcherStub';
  379. }
  380. $dumper = new PhpMatcherDumper($collection);
  381. $code = $dumper->dump($options);
  382. file_put_contents($this->dumpPath, $code);
  383. include $this->dumpPath;
  384. return $this->matcherClass;
  385. }
  386. }
  387. abstract class RedirectableUrlMatcherStub extends UrlMatcher implements RedirectableUrlMatcherInterface
  388. {
  389. public function redirect($path, $route, $scheme = null)
  390. {
  391. }
  392. }