CompiledUrlGeneratorDumperTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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\Generator\Dumper;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
  13. use Symfony\Component\Routing\Exception\RouteCircularReferenceException;
  14. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  15. use Symfony\Component\Routing\Generator\CompiledUrlGenerator;
  16. use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper;
  17. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  18. use Symfony\Component\Routing\RequestContext;
  19. use Symfony\Component\Routing\Route;
  20. use Symfony\Component\Routing\RouteCollection;
  21. class CompiledUrlGeneratorDumperTest extends TestCase
  22. {
  23. use ExpectDeprecationTrait;
  24. /**
  25. * @var RouteCollection
  26. */
  27. private $routeCollection;
  28. /**
  29. * @var CompiledUrlGeneratorDumper
  30. */
  31. private $generatorDumper;
  32. /**
  33. * @var string
  34. */
  35. private $testTmpFilepath;
  36. /**
  37. * @var string
  38. */
  39. private $largeTestTmpFilepath;
  40. protected function setUp(): void
  41. {
  42. parent::setUp();
  43. $this->routeCollection = new RouteCollection();
  44. $this->generatorDumper = new CompiledUrlGeneratorDumper($this->routeCollection);
  45. $this->testTmpFilepath = sys_get_temp_dir().'/php_generator.'.$this->getName().'.php';
  46. $this->largeTestTmpFilepath = sys_get_temp_dir().'/php_generator.'.$this->getName().'.large.php';
  47. @unlink($this->testTmpFilepath);
  48. @unlink($this->largeTestTmpFilepath);
  49. }
  50. protected function tearDown(): void
  51. {
  52. parent::tearDown();
  53. @unlink($this->testTmpFilepath);
  54. $this->routeCollection = null;
  55. $this->generatorDumper = null;
  56. $this->testTmpFilepath = null;
  57. }
  58. public function testDumpWithRoutes()
  59. {
  60. $this->routeCollection->add('Test', new Route('/testing/{foo}'));
  61. $this->routeCollection->add('Test2', new Route('/testing2'));
  62. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  63. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
  64. $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', ['foo' => 'bar'], UrlGeneratorInterface::ABSOLUTE_URL);
  65. $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', [], UrlGeneratorInterface::ABSOLUTE_URL);
  66. $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', ['foo' => 'bar'], UrlGeneratorInterface::ABSOLUTE_PATH);
  67. $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', [], UrlGeneratorInterface::ABSOLUTE_PATH);
  68. $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
  69. $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
  70. $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
  71. $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
  72. }
  73. public function testDumpWithSimpleLocalizedRoutes()
  74. {
  75. $this->routeCollection->add('test', new Route('/foo'));
  76. $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'en'));
  77. $this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'nl'));
  78. $code = $this->generatorDumper->dump();
  79. file_put_contents($this->testTmpFilepath, $code);
  80. $context = new RequestContext('/app.php');
  81. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, $context, null, 'en');
  82. $urlWithDefaultLocale = $projectUrlGenerator->generate('test');
  83. $urlWithSpecifiedLocale = $projectUrlGenerator->generate('test', ['_locale' => 'nl']);
  84. $context->setParameter('_locale', 'en');
  85. $urlWithEnglishContext = $projectUrlGenerator->generate('test');
  86. $context->setParameter('_locale', 'nl');
  87. $urlWithDutchContext = $projectUrlGenerator->generate('test');
  88. $this->assertEquals('/app.php/testing/is/fun', $urlWithDefaultLocale);
  89. $this->assertEquals('/app.php/testen/is/leuk', $urlWithSpecifiedLocale);
  90. $this->assertEquals('/app.php/testing/is/fun', $urlWithEnglishContext);
  91. $this->assertEquals('/app.php/testen/is/leuk', $urlWithDutchContext);
  92. // test with full route name
  93. $this->assertEquals('/app.php/testing/is/fun', $projectUrlGenerator->generate('test.en'));
  94. $context->setParameter('_locale', 'de_DE');
  95. // test that it fall backs to another route when there is no matching localized route
  96. $this->assertEquals('/app.php/foo', $projectUrlGenerator->generate('test'));
  97. }
  98. public function testDumpWithRouteNotFoundLocalizedRoutes()
  99. {
  100. $this->expectException(RouteNotFoundException::class);
  101. $this->expectExceptionMessage('Unable to generate a URL for the named route "test" as such route does not exist.');
  102. $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'en'));
  103. $code = $this->generatorDumper->dump();
  104. file_put_contents($this->testTmpFilepath, $code);
  105. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'), null, 'pl_PL');
  106. $projectUrlGenerator->generate('test');
  107. }
  108. public function testDumpWithFallbackLocaleLocalizedRoutes()
  109. {
  110. $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'en'));
  111. $this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'nl'));
  112. $this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'fr'));
  113. $code = $this->generatorDumper->dump();
  114. file_put_contents($this->testTmpFilepath, $code);
  115. $context = new RequestContext('/app.php');
  116. $context->setParameter('_locale', 'en_GB');
  117. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, $context, null, null);
  118. // test with context _locale
  119. $this->assertEquals('/app.php/testing/is/fun', $projectUrlGenerator->generate('test'));
  120. // test with parameters _locale
  121. $this->assertEquals('/app.php/testen/is/leuk', $projectUrlGenerator->generate('test', ['_locale' => 'nl_BE']));
  122. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'), null, 'fr_CA');
  123. // test with default locale
  124. $this->assertEquals('/app.php/tester/est/amusant', $projectUrlGenerator->generate('test'));
  125. }
  126. public function testDumpWithTooManyRoutes()
  127. {
  128. $this->routeCollection->add('Test', new Route('/testing/{foo}'));
  129. for ($i = 0; $i < 32769; ++$i) {
  130. $this->routeCollection->add('route_'.$i, new Route('/route_'.$i));
  131. }
  132. $this->routeCollection->add('Test2', new Route('/testing2'));
  133. file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump());
  134. $this->routeCollection = $this->generatorDumper = null;
  135. $projectUrlGenerator = new CompiledUrlGenerator(require $this->largeTestTmpFilepath, new RequestContext('/app.php'));
  136. $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', ['foo' => 'bar'], UrlGeneratorInterface::ABSOLUTE_URL);
  137. $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', [], UrlGeneratorInterface::ABSOLUTE_URL);
  138. $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', ['foo' => 'bar'], UrlGeneratorInterface::ABSOLUTE_PATH);
  139. $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', [], UrlGeneratorInterface::ABSOLUTE_PATH);
  140. $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
  141. $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
  142. $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
  143. $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
  144. }
  145. public function testDumpWithoutRoutes()
  146. {
  147. $this->expectException(\InvalidArgumentException::class);
  148. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  149. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
  150. $projectUrlGenerator->generate('Test', []);
  151. }
  152. public function testGenerateNonExistingRoute()
  153. {
  154. $this->expectException(RouteNotFoundException::class);
  155. $this->routeCollection->add('Test', new Route('/test'));
  156. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  157. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  158. $projectUrlGenerator->generate('NonExisting', []);
  159. }
  160. public function testDumpForRouteWithDefaults()
  161. {
  162. $this->routeCollection->add('Test', new Route('/testing/{foo}', ['foo' => 'bar']));
  163. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  164. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  165. $url = $projectUrlGenerator->generate('Test', []);
  166. $this->assertEquals('/testing', $url);
  167. }
  168. public function testDumpWithSchemeRequirement()
  169. {
  170. $this->routeCollection->add('Test1', new Route('/testing', [], [], [], '', ['ftp', 'https']));
  171. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  172. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
  173. $absoluteUrl = $projectUrlGenerator->generate('Test1', [], UrlGeneratorInterface::ABSOLUTE_URL);
  174. $relativeUrl = $projectUrlGenerator->generate('Test1', [], UrlGeneratorInterface::ABSOLUTE_PATH);
  175. $this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl);
  176. $this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl);
  177. $projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php', 'GET', 'localhost', 'https'));
  178. $absoluteUrl = $projectUrlGenerator->generate('Test1', [], UrlGeneratorInterface::ABSOLUTE_URL);
  179. $relativeUrl = $projectUrlGenerator->generate('Test1', [], UrlGeneratorInterface::ABSOLUTE_PATH);
  180. $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl);
  181. $this->assertEquals('/app.php/testing', $relativeUrl);
  182. }
  183. public function testDumpWithLocalizedRoutesPreserveTheGoodLocaleInTheUrl()
  184. {
  185. $this->routeCollection->add('foo.en', (new Route('/{_locale}/fork'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'foo')->setRequirement('_locale', 'en'));
  186. $this->routeCollection->add('foo.fr', (new Route('/{_locale}/fourchette'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'foo')->setRequirement('_locale', 'fr'));
  187. $this->routeCollection->add('fun.en', (new Route('/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'fun')->setRequirement('_locale', 'en'));
  188. $this->routeCollection->add('fun.fr', (new Route('/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'fun')->setRequirement('_locale', 'fr'));
  189. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  190. $requestContext = new RequestContext();
  191. $requestContext->setParameter('_locale', 'fr');
  192. $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, $requestContext, null, null);
  193. $this->assertSame('/fr/fourchette', $compiledUrlGenerator->generate('foo'));
  194. $this->assertSame('/en/fork', $compiledUrlGenerator->generate('foo.en'));
  195. $this->assertSame('/en/fork', $compiledUrlGenerator->generate('foo', ['_locale' => 'en']));
  196. $this->assertSame('/fr/fourchette', $compiledUrlGenerator->generate('foo.fr', ['_locale' => 'en']));
  197. $this->assertSame('/amusant', $compiledUrlGenerator->generate('fun'));
  198. $this->assertSame('/fun', $compiledUrlGenerator->generate('fun.en'));
  199. $this->assertSame('/fun', $compiledUrlGenerator->generate('fun', ['_locale' => 'en']));
  200. $this->assertSame('/amusant', $compiledUrlGenerator->generate('fun.fr', ['_locale' => 'en']));
  201. }
  202. public function testAliases()
  203. {
  204. $subCollection = new RouteCollection();
  205. $subCollection->add('a', new Route('/sub'));
  206. $subCollection->addAlias('b', 'a');
  207. $subCollection->addAlias('c', 'b');
  208. $subCollection->addNamePrefix('sub_');
  209. $this->routeCollection->add('a', new Route('/foo'));
  210. $this->routeCollection->addAlias('b', 'a');
  211. $this->routeCollection->addAlias('c', 'b');
  212. $this->routeCollection->addCollection($subCollection);
  213. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  214. $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  215. $this->assertSame('/foo', $compiledUrlGenerator->generate('b'));
  216. $this->assertSame('/foo', $compiledUrlGenerator->generate('c'));
  217. $this->assertSame('/sub', $compiledUrlGenerator->generate('sub_b'));
  218. $this->assertSame('/sub', $compiledUrlGenerator->generate('sub_c'));
  219. }
  220. public function testTargetAliasNotExisting()
  221. {
  222. $this->expectException(RouteNotFoundException::class);
  223. $this->routeCollection->addAlias('a', 'not-existing');
  224. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  225. $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  226. $compiledUrlGenerator->generate('a');
  227. }
  228. public function testTargetAliasWithNamePrefixNotExisting()
  229. {
  230. $this->expectException(RouteNotFoundException::class);
  231. $subCollection = new RouteCollection();
  232. $subCollection->addAlias('a', 'not-existing');
  233. $subCollection->addNamePrefix('sub_');
  234. $this->routeCollection->addCollection($subCollection);
  235. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  236. $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  237. $compiledUrlGenerator->generate('sub_a');
  238. }
  239. public function testCircularReferenceShouldThrowAnException()
  240. {
  241. $this->expectException(RouteCircularReferenceException::class);
  242. $this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> a -> b".');
  243. $this->routeCollection->addAlias('a', 'b');
  244. $this->routeCollection->addAlias('b', 'a');
  245. $this->generatorDumper->dump();
  246. }
  247. public function testDeepCircularReferenceShouldThrowAnException()
  248. {
  249. $this->expectException(RouteCircularReferenceException::class);
  250. $this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> c -> b".');
  251. $this->routeCollection->addAlias('a', 'b');
  252. $this->routeCollection->addAlias('b', 'c');
  253. $this->routeCollection->addAlias('c', 'b');
  254. $this->generatorDumper->dump();
  255. }
  256. public function testIndirectCircularReferenceShouldThrowAnException()
  257. {
  258. $this->expectException(RouteCircularReferenceException::class);
  259. $this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> c -> a -> b".');
  260. $this->routeCollection->addAlias('a', 'b');
  261. $this->routeCollection->addAlias('b', 'c');
  262. $this->routeCollection->addAlias('c', 'a');
  263. $this->generatorDumper->dump();
  264. }
  265. /**
  266. * @group legacy
  267. */
  268. public function testDeprecatedAlias()
  269. {
  270. $this->expectDeprecation('Since foo/bar 1.0.0: The "b" route alias is deprecated. You should stop using it, as it will be removed in the future.');
  271. $this->routeCollection->add('a', new Route('/foo'));
  272. $this->routeCollection->addAlias('b', 'a')
  273. ->setDeprecated('foo/bar', '1.0.0', '');
  274. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  275. $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  276. $compiledUrlGenerator->generate('b');
  277. }
  278. /**
  279. * @group legacy
  280. */
  281. public function testDeprecatedAliasWithCustomMessage()
  282. {
  283. $this->expectDeprecation('Since foo/bar 1.0.0: foo b.');
  284. $this->routeCollection->add('a', new Route('/foo'));
  285. $this->routeCollection->addAlias('b', 'a')
  286. ->setDeprecated('foo/bar', '1.0.0', 'foo %alias_id%.');
  287. file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
  288. $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
  289. $compiledUrlGenerator->generate('b');
  290. }
  291. /**
  292. * @group legacy
  293. */
  294. public function testTargettingADeprecatedAliasShouldTriggerDeprecation()
  295. {
  296. $this->expectDeprecation('Since foo/bar 1.0.0: foo b.');
  297. $this->routeCollection->add('a', new Route('/foo'));
  298. $this->routeCollection->addAlias('b', 'a')
  299. ->setDeprecated('foo/bar', '1.0.0', 'foo %alias_id%.');
  300. $this->routeCollection->addAlias('c', 'b');
  301. $this->generatorDumper->dump();
  302. }
  303. }