diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index 91794423234fe..fbb5cca0b0a0f 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -156,21 +156,26 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; foreach ($tokens as $token) { if ('variable' === $token[0]) { - if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + $varName = $token[3]; + if ($important = ('!' === $varName[0])) { + $varName = substr($varName, 1); + } + + if (!$optional || $important || !array_key_exists($varName, $defaults) || (null !== $mergedParams[$varName] && (string) $mergedParams[$varName] !== (string) $defaults[$varName])) { // check requirement - if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$varName])) { if ($this->strictRequirements) { - throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + throw new InvalidParameterException(strtr($message, array('{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]))); } if ($this->logger) { - $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + $this->logger->error($message, array('parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName])); } return; } - $url = $token[1].$mergedParams[$token[3]].$url; + $url = $token[1].$mergedParams[$varName].$url; $optional = false; } } else { diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 66b291b831adc..abab86be7a95b 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -111,7 +111,7 @@ private static function compilePattern(Route $route, $pattern, $isHost) // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. - preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + preg_match_all('#\{!?\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); foreach ($matches as $match) { $varName = substr($match[0][0], 1, -1); // get all static text preceding the current variable @@ -184,6 +184,9 @@ private static function compilePattern(Route $route, $pattern, $isHost) } $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); + if ('!' === $varName[0]) { + $varName = substr($varName, 1); + } $variables[] = $varName; } @@ -283,6 +286,10 @@ private static function computeRegexp(array $tokens, int $index, int $firstOptio // Text tokens return preg_quote($token[1], self::REGEX_DELIMITER); } else { + if ('variable' === $token[0] && '!' === $token[3][0]) { + $token[3] = substr($token[3], 1); + } + // Variable tokens if (0 === $index && 0 === $firstOptional) { // When the only token is an optional variable token, the separator is required diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index d4bf18ccac929..bb248d0fcd997 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -397,6 +397,27 @@ public function testDefaultRequirementOfVariable() $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html'))); } + public function testImportantVariable() + { + $routes = $this->getRoutes('test', (new Route('/{page}.{!_format}'))->addDefaults(array('_format' => 'mobile.html'))); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/index.xml', $generator->generate('test', array('page' => 'index', '_format' => 'xml'))); + $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html'))); + $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException + */ + public function testImportantVariableWithNoDefault() + { + $routes = $this->getRoutes('test', new Route('/{page}.{!_format}')); + $generator = $this->getGenerator($routes); + + $generator->generate('test', array('page' => 'index')); + } + /** * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException */ diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index bcaf3dee7218a..ecf6c5926bc5c 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -177,6 +177,15 @@ public function testMatchSpecialRouteName() $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar')); } + public function testMatchImportantVariable() + { + $collection = new RouteCollection(); + $collection->add('index', new Route('/index.{!_format}', array('_format' => 'xml'))); + + $matcher = $this->getUrlMatcher($collection); + $this->assertEquals(array('_route' => 'index', '_format' => 'xml'), $matcher->match('/index.xml')); + } + /** * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException */