From 6d79a565e0e56b525134080687eba3961eab1a73 Mon Sep 17 00:00:00 2001 From: Rhodri Pugh Date: Mon, 15 Dec 2014 10:10:55 +0000 Subject: [PATCH] [Routing] adds _fragment special option to url generation for document fragment --- .../Routing/Generator/UrlGenerator.php | 12 +++++++++++- .../Generator/UrlGeneratorInterface.php | 2 ++ .../Component/Routing/RouteCompiler.php | 14 +++++++++++--- .../Tests/Generator/UrlGeneratorTest.php | 19 +++++++++++++++++++ .../Routing/Tests/RouteCompilerTest.php | 10 ++++++++++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index bc0f6e1547..c78d1014bd 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -257,14 +257,24 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $url = $schemeAuthority.$this->context->getBaseUrl().$url; } - // add a query string if needed + // 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 if ($extra && $query = http_build_query($extra, '', '&')) { // "/" and "?" can be left decoded for better user experience, see // http://tools.ietf.org/html/rfc3986#section-3.4 $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 4f9733b03f..4521b3cd5c 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -622,6 +622,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