[FrameworkBundle] Allow using kernel parameters in routes
Kernel parameters can now be used at any position in patterns, defaults and requirements.
This commit is contained in:
parent
e71149b4d9
commit
0555913fbb
@ -38,3 +38,5 @@ CHANGELOG
|
|||||||
create the class cache.
|
create the class cache.
|
||||||
* [BC BREAK] TemplateNameParser::parseFromFilename() has been moved to a dedicated
|
* [BC BREAK] TemplateNameParser::parseFromFilename() has been moved to a dedicated
|
||||||
parser: TemplateFilenameParser::parse().
|
parser: TemplateFilenameParser::parse().
|
||||||
|
* [BC BREAK] Kernel parameters are replaced by their value whereever they appear
|
||||||
|
in Route patterns, requirements and defaults. Use '%%' as the escaped value for '%'.
|
||||||
|
@ -16,6 +16,8 @@ use Symfony\Component\Routing\RequestContext;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\Routing\RouteCollection;
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
|
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This Router only creates the Loader only when the cache is empty.
|
* This Router only creates the Loader only when the cache is empty.
|
||||||
@ -72,9 +74,12 @@ class Router extends BaseRouter implements WarmableInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces placeholders with service container parameter values in route defaults and requirements.
|
* Replaces placeholders with service container parameter values in:
|
||||||
|
* - the route defaults,
|
||||||
|
* - the route requirements,
|
||||||
|
* - the route pattern.
|
||||||
*
|
*
|
||||||
* @param $collection
|
* @param RouteCollection $collection
|
||||||
*/
|
*/
|
||||||
private function resolveParameters(RouteCollection $collection)
|
private function resolveParameters(RouteCollection $collection)
|
||||||
{
|
{
|
||||||
@ -83,27 +88,60 @@ class Router extends BaseRouter implements WarmableInterface
|
|||||||
$this->resolveParameters($route);
|
$this->resolveParameters($route);
|
||||||
} else {
|
} else {
|
||||||
foreach ($route->getDefaults() as $name => $value) {
|
foreach ($route->getDefaults() as $name => $value) {
|
||||||
if (!$value || '%' !== $value[0] || '%' !== substr($value, -1)) {
|
$route->setDefault($name, $this->resolveString($value));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = substr($value, 1, -1);
|
|
||||||
if ($this->container->hasParameter($key)) {
|
|
||||||
$route->setDefault($name, $this->container->getParameter($key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($route->getRequirements() as $name => $value) {
|
foreach ($route->getRequirements() as $name => $value) {
|
||||||
if (!$value || '%' !== $value[0] || '%' !== substr($value, -1)) {
|
$route->setRequirement($name, $this->resolveString($value));
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = substr($value, 1, -1);
|
$route->setPattern($this->resolveString($route->getPattern()));
|
||||||
if ($this->container->hasParameter($key)) {
|
|
||||||
$route->setRequirement($name, $this->container->getParameter($key));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces placeholders with the service container parameters in the given string.
|
||||||
|
*
|
||||||
|
* @param string $value The source string which might contain %placeholders%
|
||||||
|
*
|
||||||
|
* @return string A string where the placeholders have been replaced.
|
||||||
|
*
|
||||||
|
* @throws ParameterNotFoundException When a placeholder does not exist as a container parameter
|
||||||
|
* @throws RuntimeException When a container value is not a string or a numeric value
|
||||||
|
*/
|
||||||
|
private function resolveString($value)
|
||||||
|
{
|
||||||
|
$container = $this->container;
|
||||||
|
|
||||||
|
$escapedValue = preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($container, $value) {
|
||||||
|
// skip %%
|
||||||
|
if (!isset($match[1])) {
|
||||||
|
return '%%';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$key = strtolower($match[1]);
|
||||||
|
|
||||||
|
if (!$container->hasParameter($key)) {
|
||||||
|
throw new ParameterNotFoundException($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolved = $container->getParameter($key);
|
||||||
|
|
||||||
|
if (is_string($resolved) || is_numeric($resolved)) {
|
||||||
|
return (string) $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException(sprintf(
|
||||||
|
'A string value must be composed of strings and/or numbers,' .
|
||||||
|
'but found parameter "%s" of type %s inside string value "%s".',
|
||||||
|
$key,
|
||||||
|
gettype($resolved),
|
||||||
|
$value)
|
||||||
|
);
|
||||||
|
|
||||||
|
}, $value);
|
||||||
|
|
||||||
|
return str_replace('%%', '%', $escapedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,72 +17,151 @@ use Symfony\Component\Routing\RouteCollection;
|
|||||||
|
|
||||||
class RoutingTest extends \PHPUnit_Framework_TestCase
|
class RoutingTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
public function testPlaceholders()
|
public function testDefaultsPlaceholders()
|
||||||
{
|
{
|
||||||
$routes = new RouteCollection();
|
$routes = new RouteCollection();
|
||||||
$routes->add('foo', new Route('/foo', array(
|
|
||||||
'foo' => '%foo%',
|
$routes->add('foo', new Route(
|
||||||
'bar' => '%bar%',
|
'/foo',
|
||||||
'foobar' => 'foobar',
|
array(
|
||||||
'foo1' => '%foo',
|
'foo' => 'before_%parameter.foo%',
|
||||||
'foo2' => 'foo%',
|
'bar' => '%parameter.bar%_after',
|
||||||
'foo3' => 'f%o%o',
|
'baz' => '%%unescaped%%',
|
||||||
), array(
|
),
|
||||||
'foo' => '%foo%',
|
array(
|
||||||
'bar' => '%bar%',
|
)
|
||||||
'foobar' => 'foobar',
|
));
|
||||||
'foo1' => '%foo',
|
|
||||||
'foo2' => 'foo%',
|
|
||||||
'foo3' => 'f%o%o',
|
|
||||||
)));
|
|
||||||
|
|
||||||
$sc = $this->getServiceContainer($routes);
|
$sc = $this->getServiceContainer($routes);
|
||||||
$sc->expects($this->at(1))->method('hasParameter')->will($this->returnValue(false));
|
|
||||||
$sc->expects($this->at(2))->method('hasParameter')->will($this->returnValue(true));
|
$sc->expects($this->at(1))->method('hasParameter')->will($this->returnValue(true));
|
||||||
$sc->expects($this->at(3))->method('getParameter')->will($this->returnValue('bar'));
|
$sc->expects($this->at(2))->method('getParameter')->will($this->returnValue('foo'));
|
||||||
$sc->expects($this->at(4))->method('hasParameter')->will($this->returnValue(false));
|
$sc->expects($this->at(3))->method('hasParameter')->will($this->returnValue(true));
|
||||||
$sc->expects($this->at(5))->method('hasParameter')->will($this->returnValue(true));
|
$sc->expects($this->at(4))->method('getParameter')->will($this->returnValue('bar'));
|
||||||
$sc->expects($this->at(6))->method('getParameter')->will($this->returnValue('bar'));
|
|
||||||
|
|
||||||
$router = new Router($sc, 'foo');
|
$router = new Router($sc, 'foo');
|
||||||
$route = $router->getRouteCollection()->get('foo');
|
$route = $router->getRouteCollection()->get('foo');
|
||||||
|
|
||||||
$this->assertEquals('%foo%', $route->getDefault('foo'));
|
$this->assertEquals(
|
||||||
$this->assertEquals('bar', $route->getDefault('bar'));
|
array(
|
||||||
$this->assertEquals('foobar', $route->getDefault('foobar'));
|
'foo' => 'before_foo',
|
||||||
$this->assertEquals('%foo', $route->getDefault('foo1'));
|
'bar' => 'bar_after',
|
||||||
$this->assertEquals('foo%', $route->getDefault('foo2'));
|
'baz' => '%unescaped%',
|
||||||
$this->assertEquals('f%o%o', $route->getDefault('foo3'));
|
),
|
||||||
|
$route->getDefaults()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertEquals('%foo%', $route->getRequirement('foo'));
|
public function testRequirementsPlaceholders()
|
||||||
$this->assertEquals('bar', $route->getRequirement('bar'));
|
{
|
||||||
$this->assertEquals('foobar', $route->getRequirement('foobar'));
|
$routes = new RouteCollection();
|
||||||
$this->assertEquals('%foo', $route->getRequirement('foo1'));
|
|
||||||
$this->assertEquals('foo%', $route->getRequirement('foo2'));
|
$routes->add('foo', new Route(
|
||||||
$this->assertEquals('f%o%o', $route->getRequirement('foo3'));
|
'/foo',
|
||||||
|
array(
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'foo' => 'before_%parameter.foo%',
|
||||||
|
'bar' => '%parameter.bar%_after',
|
||||||
|
'baz' => '%%unescaped%%',
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
$sc = $this->getServiceContainer($routes);
|
||||||
|
|
||||||
|
$sc->expects($this->at(1))->method('hasParameter')->with('parameter.foo')->will($this->returnValue(true));
|
||||||
|
$sc->expects($this->at(2))->method('getParameter')->with('parameter.foo')->will($this->returnValue('foo'));
|
||||||
|
$sc->expects($this->at(3))->method('hasParameter')->with('parameter.bar')->will($this->returnValue(true));
|
||||||
|
$sc->expects($this->at(4))->method('getParameter')->with('parameter.bar')->will($this->returnValue('bar'));
|
||||||
|
|
||||||
|
$router = new Router($sc, 'foo');
|
||||||
|
$route = $router->getRouteCollection()->get('foo');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array(
|
||||||
|
'foo' => 'before_foo',
|
||||||
|
'bar' => 'bar_after',
|
||||||
|
'baz' => '%unescaped%',
|
||||||
|
),
|
||||||
|
$route->getRequirements()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPatternPlaceholders()
|
||||||
|
{
|
||||||
|
$routes = new RouteCollection();
|
||||||
|
|
||||||
|
$routes->add('foo', new Route('/before/%parameter.foo%/after/%%unescaped%%'));
|
||||||
|
|
||||||
|
$sc = $this->getServiceContainer($routes);
|
||||||
|
|
||||||
|
$sc->expects($this->at(1))->method('hasParameter')->with('parameter.foo')->will($this->returnValue(true));
|
||||||
|
$sc->expects($this->at(2))->method('getParameter')->with('parameter.foo')->will($this->returnValue('foo'));
|
||||||
|
|
||||||
|
$router = new Router($sc, 'foo');
|
||||||
|
$route = $router->getRouteCollection()->get('foo');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'/before/foo/after/%unescaped%',
|
||||||
|
$route->getPattern()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException
|
||||||
|
* @expectedExceptionMessage You have requested a non-existent parameter "nope".
|
||||||
|
*/
|
||||||
|
public function testExceptionOnNonExistentParameter()
|
||||||
|
{
|
||||||
|
$routes = new RouteCollection();
|
||||||
|
|
||||||
|
$routes->add('foo', new Route('/%nope%'));
|
||||||
|
|
||||||
|
$sc = $this->getServiceContainer($routes);
|
||||||
|
|
||||||
|
$sc->expects($this->at(1))->method('hasParameter')->with('nope')->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$router = new Router($sc, 'foo');
|
||||||
|
$router->getRouteCollection()->get('foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage A string value must be composed of strings and/or numbers,but found parameter "object" of type object inside string value "/%object%".
|
||||||
|
*/
|
||||||
|
public function testExceptionOnNonStringParameter()
|
||||||
|
{
|
||||||
|
$routes = new RouteCollection();
|
||||||
|
|
||||||
|
$routes->add('foo', new Route('/%object%'));
|
||||||
|
|
||||||
|
$sc = $this->getServiceContainer($routes);
|
||||||
|
|
||||||
|
$sc->expects($this->at(1))->method('hasParameter')->with('object')->will($this->returnValue(true));
|
||||||
|
$sc->expects($this->at(2))->method('getParameter')->with('object')->will($this->returnValue(new \stdClass()));
|
||||||
|
|
||||||
|
$router = new Router($sc, 'foo');
|
||||||
|
$router->getRouteCollection()->get('foo');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getServiceContainer(RouteCollection $routes)
|
private function getServiceContainer(RouteCollection $routes)
|
||||||
{
|
|
||||||
$sc = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
|
||||||
$sc
|
|
||||||
->expects($this->once())
|
|
||||||
->method('get')
|
|
||||||
->will($this->returnValue($this->getLoader($routes)))
|
|
||||||
;
|
|
||||||
|
|
||||||
return $sc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getLoader(RouteCollection $routes)
|
|
||||||
{
|
{
|
||||||
$loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface');
|
$loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface');
|
||||||
|
|
||||||
$loader
|
$loader
|
||||||
->expects($this->any())
|
->expects($this->any())
|
||||||
->method('load')
|
->method('load')
|
||||||
->will($this->returnValue($routes))
|
->will($this->returnValue($routes))
|
||||||
;
|
;
|
||||||
|
|
||||||
return $loader;
|
$sc = $this->getMock('Symfony\\Component\\DependencyInjection\\ContainerInterface');
|
||||||
|
|
||||||
|
$sc
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->will($this->returnValue($loader))
|
||||||
|
;
|
||||||
|
|
||||||
|
return $sc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user