diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index 127435c3f8..cdf2b6b4fd 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -257,6 +257,13 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $url = $schemeAuthority.$this->context->getBaseUrl().$url; } + // extract unused parameters + $extra = array_diff_key($parameters, $variables, $defaults); + + // extract fragment + $fragment = isset($extra['_fragment']) ? $extra['_fragment'] : ''; + unset($extra['_fragment']); + // add a query string if needed $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) { return $a == $b ? 0 : 1; @@ -268,6 +275,10 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $url .= '?'.strtr($query, array('%2F' => '/')); } + if ('' !== $fragment) { + $url .= '#'.strtr(rawurlencode($fragment), array('%2F' => '/', '%3F' => '?')); + } + return $url; } diff --git a/src/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php b/src/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php index f501ebd9a8..d6e7938e5b 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php +++ b/src/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php @@ -69,6 +69,8 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface * * If there is no route with the given name, the generator must throw the RouteNotFoundException. * + * The special parameter _fragment will be used as the document fragment suffixed to the final URL. + * * @param string $name The name of the route * @param mixed $parameters An array of parameters * @param int $referenceType The type of reference to be generated (one of the constants) diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index f6637da666..a60f0bb34e 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -31,9 +31,10 @@ class RouteCompiler implements RouteCompilerInterface /** * {@inheritdoc} * - * @throws \LogicException If a variable is referenced more than once - * @throws \DomainException If a variable name is numeric because PHP raises an error for such - * subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)". + * @throws \InvalidArgumentException If a path variable is named _fragment + * @throws \LogicException If a variable is referenced more than once + * @throws \DomainException If a variable name is numeric because PHP raises an error for such + * subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)". */ public static function compile(Route $route) { @@ -59,6 +60,13 @@ class RouteCompiler implements RouteCompilerInterface $staticPrefix = $result['staticPrefix']; $pathVariables = $result['variables']; + + foreach ($pathVariables as $pathParam) { + if ('_fragment' === $pathParam) { + throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath())); + } + } + $variables = array_merge($variables, $pathVariables); $tokens = $result['tokens']; diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 8b6c0d1417..2ac06ddd3c 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -634,6 +634,25 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase ); } + public function testFragmentsCanBeAppendedToUrls() + { + $routes = $this->getRoutes('test', new Route('/testing')); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => 'frag ment'), true); + $this->assertEquals('/app.php/testing#frag%20ment', $url); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '0'), true); + $this->assertEquals('/app.php/testing#0', $url); + } + + public function testFragmentsDoNotEscapeValidCharacters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '?/'), true); + + $this->assertEquals('/app.php/testing#?/', $url); + } + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) { $context = new RequestContext('/app.php'); diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index b4b4f45a83..2e9120e8db 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -175,6 +175,16 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase $compiled = $route->compile(); } + /** + * @expectedException \InvalidArgumentException + */ + public function testRouteWithFragmentAsPathParameter() + { + $route = new Route('/{_fragment}'); + + $compiled = $route->compile(); + } + /** * @dataProvider getNumericVariableNames * @expectedException \DomainException