4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Routing\Tests\Generator
;
14 use Symfony\Component\Routing\RouteCollection
;
15 use Symfony\Component\Routing\Route
;
16 use Symfony\Component\Routing\Generator\UrlGenerator
;
17 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
;
18 use Symfony\Component\Routing\RequestContext
;
20 class UrlGeneratorTest
extends \PHPUnit_Framework_TestCase
22 public function testAbsoluteUrlWithPort80()
24 $routes = $this->getRoutes('test', new Route('/testing'));
25 $url = $this->getGenerator($routes)->generate('test', array(), true);
27 $this->assertEquals('http://localhost/app.php/testing', $url);
30 public function testAbsoluteSecureUrlWithPort443()
32 $routes = $this->getRoutes('test', new Route('/testing'));
33 $url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), true);
35 $this->assertEquals('https://localhost/app.php/testing', $url);
38 public function testAbsoluteUrlWithNonStandardPort()
40 $routes = $this->getRoutes('test', new Route('/testing'));
41 $url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), true);
43 $this->assertEquals('http://localhost:8080/app.php/testing', $url);
46 public function testAbsoluteSecureUrlWithNonStandardPort()
48 $routes = $this->getRoutes('test', new Route('/testing'));
49 $url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), true);
51 $this->assertEquals('https://localhost:8080/app.php/testing', $url);
54 public function testRelativeUrlWithoutParameters()
56 $routes = $this->getRoutes('test', new Route('/testing'));
57 $url = $this->getGenerator($routes)->generate('test', array(), false);
59 $this->assertEquals('/app.php/testing', $url);
62 public function testRelativeUrlWithParameter()
64 $routes = $this->getRoutes('test', new Route('/testing/{foo}'));
65 $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), false);
67 $this->assertEquals('/app.php/testing/bar', $url);
70 public function testRelativeUrlWithNullParameter()
72 $routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null)));
73 $url = $this->getGenerator($routes)->generate('test', array(), false);
75 $this->assertEquals('/app.php/testing', $url);
79 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
81 public function testRelativeUrlWithNullParameterButNotOptional()
83 $routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null)));
84 // This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params.
85 // Generating path "/testing//bar" would be wrong as matching this route would fail.
86 $this->getGenerator($routes)->generate('test', array(), false);
89 public function testRelativeUrlWithOptionalZeroParameter()
91 $routes = $this->getRoutes('test', new Route('/testing/{page}'));
92 $url = $this->getGenerator($routes)->generate('test', array('page' => 0), false);
94 $this->assertEquals('/app.php/testing/0', $url);
97 public function testNotPassedOptionalParameterInBetween()
99 $routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0)));
100 $this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1)));
101 $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test'));
104 public function testRelativeUrlWithExtraParameters()
106 $routes = $this->getRoutes('test', new Route('/testing'));
107 $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), false);
109 $this->assertEquals('/app.php/testing?foo=bar', $url);
112 public function testAbsoluteUrlWithExtraParameters()
114 $routes = $this->getRoutes('test', new Route('/testing'));
115 $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true);
117 $this->assertEquals('http://localhost/app.php/testing?foo=bar', $url);
120 public function testUrlWithNullExtraParameters()
122 $routes = $this->getRoutes('test', new Route('/testing'));
123 $url = $this->getGenerator($routes)->generate('test', array('foo' => null), true);
125 $this->assertEquals('http://localhost/app.php/testing', $url);
128 public function testUrlWithExtraParametersFromGlobals()
130 $routes = $this->getRoutes('test', new Route('/testing'));
131 $generator = $this->getGenerator($routes);
132 $context = new RequestContext('/app.php');
133 $context->setParameter('bar', 'bar');
134 $generator->setContext($context);
135 $url = $generator->generate('test', array('foo' => 'bar'));
137 $this->assertEquals('/app.php/testing?foo=bar', $url);
140 public function testUrlWithGlobalParameter()
142 $routes = $this->getRoutes('test', new Route('/testing/{foo}'));
143 $generator = $this->getGenerator($routes);
144 $context = new RequestContext('/app.php');
145 $context->setParameter('foo', 'bar');
146 $generator->setContext($context);
147 $url = $generator->generate('test', array());
149 $this->assertEquals('/app.php/testing/bar', $url);
152 public function testGlobalParameterHasHigherPriorityThanDefault()
154 $routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en')));
155 $generator = $this->getGenerator($routes);
156 $context = new RequestContext('/app.php');
157 $context->setParameter('_locale', 'de');
158 $generator->setContext($context);
159 $url = $generator->generate('test', array());
161 $this->assertSame('/app.php/de', $url);
165 * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
167 public function testGenerateWithoutRoutes()
169 $routes = $this->getRoutes('foo', new Route('/testing/{foo}'));
170 $this->getGenerator($routes)->generate('test', array(), true);
174 * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException
176 public function testGenerateForRouteWithoutMandatoryParameter()
178 $routes = $this->getRoutes('test', new Route('/testing/{foo}'));
179 $this->getGenerator($routes)->generate('test', array(), true);
183 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
185 public function testGenerateForRouteWithInvalidOptionalParameter()
187 $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
188 $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true);
192 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
194 public function testGenerateForRouteWithInvalidParameter()
196 $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2')));
197 $this->getGenerator($routes)->generate('test', array('foo' => '0'), true);
200 public function testGenerateForRouteWithInvalidOptionalParameterNonStrict()
202 $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
203 $generator = $this->getGenerator($routes);
204 $generator->setStrictRequirements(false);
205 $this->assertNull($generator->generate('test', array('foo' => 'bar'), true));
208 public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger()
210 if (!interface_exists('Psr\Log\LoggerInterface')) {
211 $this->markTestSkipped('The "psr/log" package is not available');
214 $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
215 $logger = $this->getMock('Psr\Log\LoggerInterface');
216 $logger->expects($this->once())
218 $generator = $this->getGenerator($routes, array(), $logger);
219 $generator->setStrictRequirements(false);
220 $this->assertNull($generator->generate('test', array('foo' => 'bar'), true));
223 public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck()
225 $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
226 $generator = $this->getGenerator($routes);
227 $generator->setStrictRequirements(null);
228 $this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar')));
232 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
234 public function testGenerateForRouteWithInvalidMandatoryParameter()
236 $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+')));
237 $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true);
241 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
243 public function testRequiredParamAndEmptyPassed()
245 $routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+')));
246 $this->getGenerator($routes)->generate('test', array('slug' => ''));
249 public function testSchemeRequirementDoesNothingIfSameCurrentScheme()
251 $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'http')));
252 $this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test'));
254 $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'https')));
255 $this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
258 public function testSchemeRequirementForcesAbsoluteUrl()
260 $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'https')));
261 $this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test'));
263 $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'http')));
264 $this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
267 public function testPathWithTwoStartingSlashes()
269 $routes = $this->getRoutes('test', new Route('//path-and-not-domain'));
271 // this must not generate '//path-and-not-domain' because that would be a network path
272 $this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test'));
275 public function testNoTrailingSlashForMultipleOptionalParameters()
277 $routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null)));
279 $this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo')));
282 public function testWithAnIntegerAsADefaultValue()
284 $routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0)));
286 $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo')));
289 public function testNullForOptionalParameterIsIgnored()
291 $routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0)));
293 $this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null)));
296 public function testQueryParamSameAsDefault()
298 $routes = $this->getRoutes('test', new Route('/test', array('default' => 'value')));
300 $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => 'foo')));
301 $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => 'value')));
302 $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test'));
305 public function testGenerateWithSpecialRouteName()
307 $routes = $this->getRoutes('$péß^a|', new Route('/bar'));
309 $this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|'));
312 public function testUrlEncoding()
314 // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986)
315 // and other special ASCII chars. These chars are tested as static text path, variable path and query param.
316 $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id';
317 $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+')));
318 $this->assertSame('/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id'
319 .'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id'
320 .'?query=%40%3A%5B%5D%2F%28%29%2A%27%22+%2B%2C%3B-._%7E%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id',
321 $this->getGenerator($routes)->generate('test', array(
328 public function testEncodingOfRelativePathSegments()
330 $routes = $this->getRoutes('test', new Route('/dir/../dir/..'));
331 $this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test'));
332 $routes = $this->getRoutes('test', new Route('/dir/./dir/.'));
333 $this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test'));
334 $routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...'));
335 $this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test'));
338 public function testAdjacentVariables()
340 $routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+')));
341 $generator = $this->getGenerator($routes);
342 $this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123')));
343 $this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml')));
345 // The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything
346 // and following optional variables like _format could never match.
347 $this->setExpectedException('Symfony\Component\Routing\Exception\InvalidParameterException');
348 $generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml'));
351 public function testOptionalVariableWithNoRealSeparator()
353 $routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All')));
354 $generator = $this->getGenerator($routes);
356 $this->assertSame('/app.php/get', $generator->generate('test'));
357 $this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites')));
360 public function testRequiredVariableWithNoRealSeparator()
362 $routes = $this->getRoutes('test', new Route('/get{what}Suffix'));
363 $generator = $this->getGenerator($routes);
365 $this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites')));
368 public function testDefaultRequirementOfVariable()
370 $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
371 $generator = $this->getGenerator($routes);
373 $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html')));
377 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
379 public function testDefaultRequirementOfVariableDisallowsSlash()
381 $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
382 $this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash'));
386 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
388 public function testDefaultRequirementOfVariableDisallowsNextSeparator()
390 $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
391 $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html'));
394 public function testWithHostDifferentFromContext()
396 $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
398 $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' =>'Fabien', 'locale' => 'fr')));
401 public function testWithHostSameAsContext()
403 $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
405 $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' =>'Fabien', 'locale' => 'fr')));
408 public function testWithHostSameAsContextAndAbsolute()
410 $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
412 $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' =>'Fabien', 'locale' => 'fr'), true));
416 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
418 public function testUrlWithInvalidParameterInHost()
420 $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
421 $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false);
425 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
427 public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue()
429 $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com'));
430 $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false);
434 * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
436 public function testUrlWithInvalidParameterEqualsDefaultValueInHost()
438 $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com'));
439 $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false);
442 public function testUrlWithInvalidParameterInHostInNonStrictMode()
444 $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
445 $generator = $this->getGenerator($routes);
446 $generator->setStrictRequirements(false);
447 $this->assertNull($generator->generate('test', array('foo' => 'baz'), false));
450 public function testGenerateNetworkPath()
452 $routes = $this->getRoutes('test', new Route('/{name}', array(), array('_scheme' => 'http'), array(), '{locale}.example.com'));
454 $this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test',
455 array('name' =>'Fabien', 'locale' => 'fr'), UrlGeneratorInterface
::NETWORK_PATH
), 'network path with different host'
457 $this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test',
458 array('name' =>'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface
::NETWORK_PATH
), 'network path although host same as context'
460 $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test',
461 array('name' =>'Fabien', 'locale' => 'fr'), UrlGeneratorInterface
::NETWORK_PATH
), 'absolute URL because scheme requirement does not match context'
463 $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test',
464 array('name' =>'Fabien', 'locale' => 'fr'), UrlGeneratorInterface
::ABSOLUTE_URL
), 'absolute URL with same scheme because it is requested'
468 public function testGenerateRelativePath()
470 $routes = new RouteCollection();
471 $routes->add('article', new Route('/{author}/{article}/'));
472 $routes->add('comments', new Route('/{author}/{article}/comments'));
473 $routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com'));
474 $routes->add('scheme', new Route('/{author}', array(), array('_scheme' => 'https')));
475 $routes->add('unrelated', new Route('/about'));
477 $generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/'));
479 $this->assertSame('comments', $generator->generate('comments',
480 array('author' =>'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface
::RELATIVE_PATH
)
482 $this->assertSame('comments?page=2', $generator->generate('comments',
483 array('author' =>'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface
::RELATIVE_PATH
)
485 $this->assertSame('../twig-is-great/', $generator->generate('article',
486 array('author' =>'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface
::RELATIVE_PATH
)
488 $this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article',
489 array('author' =>'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface
::RELATIVE_PATH
)
491 $this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host',
492 array('author' =>'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface
::RELATIVE_PATH
)
494 $this->assertSame('https://example.com/app.php/bernhard', $generator->generate('scheme',
495 array('author' =>'bernhard'), UrlGeneratorInterface
::RELATIVE_PATH
)
497 $this->assertSame('../../about', $generator->generate('unrelated',
498 array(), UrlGeneratorInterface
::RELATIVE_PATH
)
503 * @dataProvider provideRelativePaths
505 public function testGetRelativePath($sourcePath, $targetPath, $expectedPath)
507 $this->assertSame($expectedPath, UrlGenerator
::getRelativePath($sourcePath, $targetPath));
510 public function provideRelativePaths()
535 '/dir/different-file.html',
536 'different-file.html'
539 '/same/dir/extra-file',
549 '/parent/dir/extra-file',
584 '/special-chars/sp%20ce/1€/mäh/e=mc²',
585 '/special-chars/sp%20ce/1€/<µ>/e=mc²',
600 '/dir/file:with-colon',
605 '/dir/subdir/file:with-colon',
606 'subdir/file:with-colon'
616 protected function getGenerator(RouteCollection
$routes, array $parameters = array(), $logger = null)
618 $context = new RequestContext('/app.php');
619 foreach ($parameters as $key => $value) {
620 $method = 'set'.$key;
621 $context->$method($value);
623 $generator = new UrlGenerator($routes, $context, $logger);
628 protected function getRoutes($name, Route
$route)
630 $routes = new RouteCollection();
631 $routes->add($name, $route);