feature #26284 [Routing] allow no-slash root on imported routes (nicolas-grekas)
This PR was merged into the 4.1-dev branch.
Discussion
----------
[Routing] allow no-slash root on imported routes
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #12141
| License | MIT
| Doc PR | -
With this change, a collection is imported, its root can have no slash appended. e.g.:
```yaml
import:
resource: ...
trailing_slash_on_root: false
```
Works also for XML and PHP-DSL.
Commits
-------
5a98515242
[Routing] allow no-slash root on imported routes
This commit is contained in:
commit
7262c5973d
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Routing\Loader\Configurator;
|
namespace Symfony\Component\Routing\Loader\Configurator;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
use Symfony\Component\Routing\RouteCollection;
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,16 +41,18 @@ class ImportConfigurator
|
|||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
final public function prefix($prefix, string $namePrefix = '')
|
final public function prefix($prefix, bool $trailingSlashOnRoot = true)
|
||||||
{
|
{
|
||||||
if ('' !== $namePrefix) {
|
|
||||||
$this->route->addNamePrefix($namePrefix);
|
|
||||||
}
|
|
||||||
if (!$prefix) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
if (!\is_array($prefix)) {
|
if (!\is_array($prefix)) {
|
||||||
$this->route->addPrefix($prefix);
|
$this->route->addPrefix($prefix);
|
||||||
|
if (!$trailingSlashOnRoot) {
|
||||||
|
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
|
||||||
|
foreach ($this->route->all() as $route) {
|
||||||
|
if ($route->getPath() === $rootPath) {
|
||||||
|
$route->setPath(rtrim($rootPath, '/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($prefix as $locale => $localePrefix) {
|
foreach ($prefix as $locale => $localePrefix) {
|
||||||
$prefix[$locale] = trim(trim($localePrefix), '/');
|
$prefix[$locale] = trim(trim($localePrefix), '/');
|
||||||
@ -61,13 +64,13 @@ class ImportConfigurator
|
|||||||
$localizedRoute = clone $route;
|
$localizedRoute = clone $route;
|
||||||
$localizedRoute->setDefault('_locale', $locale);
|
$localizedRoute->setDefault('_locale', $locale);
|
||||||
$localizedRoute->setDefault('_canonical_route', $name);
|
$localizedRoute->setDefault('_canonical_route', $name);
|
||||||
$localizedRoute->setPath($localePrefix.$route->getPath());
|
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||||
$this->route->add($name.'.'.$locale, $localizedRoute);
|
$this->route->add($name.'.'.$locale, $localizedRoute);
|
||||||
}
|
}
|
||||||
} elseif (!isset($prefix[$locale])) {
|
} elseif (!isset($prefix[$locale])) {
|
||||||
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
|
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
|
||||||
} else {
|
} else {
|
||||||
$route->setPath($prefix[$locale].$route->getPath());
|
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||||
$this->route->add($name, $route);
|
$this->route->add($name, $route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,4 +78,16 @@ class ImportConfigurator
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the prefix to add to the name of all child routes.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function namePrefix(string $namePrefix)
|
||||||
|
{
|
||||||
|
$this->route->addNamePrefix($namePrefix);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
$host = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
|
$host = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
|
||||||
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
||||||
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
|
||||||
|
$trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true;
|
||||||
|
|
||||||
list($defaults, $requirements, $options, $condition, /* $paths */, $prefixes) = $this->parseConfigs($node, $path);
|
list($defaults, $requirements, $options, $condition, /* $paths */, $prefixes) = $this->parseConfigs($node, $path);
|
||||||
|
|
||||||
@ -172,6 +173,14 @@ class XmlFileLoader extends FileLoader
|
|||||||
|
|
||||||
if ('' !== $prefix || !$prefixes) {
|
if ('' !== $prefix || !$prefixes) {
|
||||||
$subCollection->addPrefix($prefix);
|
$subCollection->addPrefix($prefix);
|
||||||
|
if (!$trailingSlashOnRoot) {
|
||||||
|
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
|
||||||
|
foreach ($subCollection->all() as $route) {
|
||||||
|
if ($route->getPath() === $rootPath) {
|
||||||
|
$route->setPath(rtrim($rootPath, '/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($prefixes as $locale => $localePrefix) {
|
foreach ($prefixes as $locale => $localePrefix) {
|
||||||
$prefixes[$locale] = trim(trim($localePrefix), '/');
|
$prefixes[$locale] = trim(trim($localePrefix), '/');
|
||||||
@ -181,7 +190,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
$subCollection->remove($name);
|
$subCollection->remove($name);
|
||||||
foreach ($prefixes as $locale => $localePrefix) {
|
foreach ($prefixes as $locale => $localePrefix) {
|
||||||
$localizedRoute = clone $route;
|
$localizedRoute = clone $route;
|
||||||
$localizedRoute->setPath($localePrefix.$route->getPath());
|
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||||
$localizedRoute->setDefault('_locale', $locale);
|
$localizedRoute->setDefault('_locale', $locale);
|
||||||
$localizedRoute->setDefault('_canonical_route', $name);
|
$localizedRoute->setDefault('_canonical_route', $name);
|
||||||
$subCollection->add($name.'.'.$locale, $localizedRoute);
|
$subCollection->add($name.'.'.$locale, $localizedRoute);
|
||||||
@ -189,7 +198,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
} elseif (!isset($prefixes[$locale])) {
|
} elseif (!isset($prefixes[$locale])) {
|
||||||
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $path));
|
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $path));
|
||||||
} else {
|
} else {
|
||||||
$route->setPath($prefixes[$locale].$route->getPath());
|
$route->setPath($prefixes[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||||
$subCollection->add($name, $route);
|
$subCollection->add($name, $route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ use Symfony\Component\Config\Loader\FileLoader;
|
|||||||
class YamlFileLoader extends FileLoader
|
class YamlFileLoader extends FileLoader
|
||||||
{
|
{
|
||||||
private static $availableKeys = array(
|
private static $availableKeys = array(
|
||||||
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix',
|
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root',
|
||||||
);
|
);
|
||||||
private $yamlParser;
|
private $yamlParser;
|
||||||
|
|
||||||
@ -155,6 +155,7 @@ class YamlFileLoader extends FileLoader
|
|||||||
$condition = isset($config['condition']) ? $config['condition'] : null;
|
$condition = isset($config['condition']) ? $config['condition'] : null;
|
||||||
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
|
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
|
||||||
$methods = isset($config['methods']) ? $config['methods'] : null;
|
$methods = isset($config['methods']) ? $config['methods'] : null;
|
||||||
|
$trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true;
|
||||||
|
|
||||||
if (isset($config['controller'])) {
|
if (isset($config['controller'])) {
|
||||||
$defaults['_controller'] = $config['controller'];
|
$defaults['_controller'] = $config['controller'];
|
||||||
@ -167,6 +168,14 @@ class YamlFileLoader extends FileLoader
|
|||||||
|
|
||||||
if (!\is_array($prefix)) {
|
if (!\is_array($prefix)) {
|
||||||
$subCollection->addPrefix($prefix);
|
$subCollection->addPrefix($prefix);
|
||||||
|
if (!$trailingSlashOnRoot) {
|
||||||
|
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
|
||||||
|
foreach ($subCollection->all() as $route) {
|
||||||
|
if ($route->getPath() === $rootPath) {
|
||||||
|
$route->setPath(rtrim($rootPath, '/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($prefix as $locale => $localePrefix) {
|
foreach ($prefix as $locale => $localePrefix) {
|
||||||
$prefix[$locale] = trim(trim($localePrefix), '/');
|
$prefix[$locale] = trim(trim($localePrefix), '/');
|
||||||
@ -178,13 +187,13 @@ class YamlFileLoader extends FileLoader
|
|||||||
$localizedRoute = clone $route;
|
$localizedRoute = clone $route;
|
||||||
$localizedRoute->setDefault('_locale', $locale);
|
$localizedRoute->setDefault('_locale', $locale);
|
||||||
$localizedRoute->setDefault('_canonical_route', $name);
|
$localizedRoute->setDefault('_canonical_route', $name);
|
||||||
$localizedRoute->setPath($localePrefix.$route->getPath());
|
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||||
$subCollection->add($name.'.'.$locale, $localizedRoute);
|
$subCollection->add($name.'.'.$locale, $localizedRoute);
|
||||||
}
|
}
|
||||||
} elseif (!isset($prefix[$locale])) {
|
} elseif (!isset($prefix[$locale])) {
|
||||||
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $file));
|
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $file));
|
||||||
} else {
|
} else {
|
||||||
$route->setPath($prefix[$locale].$route->getPath());
|
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
|
||||||
$subCollection->add($name, $route);
|
$subCollection->add($name, $route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
<xsd:attribute name="schemes" type="xsd:string" />
|
<xsd:attribute name="schemes" type="xsd:string" />
|
||||||
<xsd:attribute name="methods" type="xsd:string" />
|
<xsd:attribute name="methods" type="xsd:string" />
|
||||||
<xsd:attribute name="controller" type="xsd:string" />
|
<xsd:attribute name="controller" type="xsd:string" />
|
||||||
|
<xsd:attribute name="trailing-slash-on-root" type="xsd:boolean" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="default" mixed="true">
|
<xsd:complexType name="default" mixed="true">
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<routes xmlns="http://symfony.com/schema/routing"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/routing
|
||||||
|
http://symfony.com/schema/routing/routing-1.0.xsd">
|
||||||
|
|
||||||
|
<import resource="../controller/routing.xml" prefix="/slash" name-prefix="a_" />
|
||||||
|
<import resource="../controller/routing.xml" prefix="/no-slash" name-prefix="b_" trailing-slash-on-root="false" />
|
||||||
|
|
||||||
|
</routes>
|
@ -0,0 +1,10 @@
|
|||||||
|
app:
|
||||||
|
resource: ../controller/routing.yml
|
||||||
|
name_prefix: a_
|
||||||
|
prefix: /slash
|
||||||
|
|
||||||
|
api:
|
||||||
|
resource: ../controller/routing.yml
|
||||||
|
name_prefix: b_
|
||||||
|
prefix: /no-slash
|
||||||
|
trailing_slash_on_root: false
|
@ -16,7 +16,11 @@ return function (RoutingConfigurator $routes) {
|
|||||||
->requirements(array('id' => '\d+'));
|
->requirements(array('id' => '\d+'));
|
||||||
|
|
||||||
$routes->import('php_dsl_sub.php')
|
$routes->import('php_dsl_sub.php')
|
||||||
->prefix('/zub', 'z_');
|
->namePrefix('z_')
|
||||||
|
->prefix('/zub');
|
||||||
|
|
||||||
|
$routes->import('php_dsl_sub_root.php')
|
||||||
|
->prefix('/bus', false);
|
||||||
|
|
||||||
$routes->add('ouf', '/ouf')
|
$routes->add('ouf', '/ouf')
|
||||||
->schemes(array('https'))
|
->schemes(array('https'))
|
||||||
|
@ -6,6 +6,7 @@ return function (RoutingConfigurator $routes) {
|
|||||||
$add = $routes->collection('c_')
|
$add = $routes->collection('c_')
|
||||||
->prefix('pub');
|
->prefix('pub');
|
||||||
|
|
||||||
|
$add('root', '/');
|
||||||
$add('bar', '/bar');
|
$add('bar', '/bar');
|
||||||
|
|
||||||
$add->collection('pub_')
|
$add->collection('pub_')
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Routing\Loader\Configurator;
|
||||||
|
|
||||||
|
return function (RoutingConfigurator $routes) {
|
||||||
|
$add = $routes->collection('r_');
|
||||||
|
|
||||||
|
$add('root', '/');
|
||||||
|
$add('bar', '/bar/');
|
||||||
|
};
|
@ -99,6 +99,9 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
$expectedCollection->add('buz', (new Route('/zub'))
|
$expectedCollection->add('buz', (new Route('/zub'))
|
||||||
->setDefaults(array('_controller' => 'foo:act'))
|
->setDefaults(array('_controller' => 'foo:act'))
|
||||||
);
|
);
|
||||||
|
$expectedCollection->add('c_root', (new Route('/sub/pub/'))
|
||||||
|
->setRequirements(array('id' => '\d+'))
|
||||||
|
);
|
||||||
$expectedCollection->add('c_bar', (new Route('/sub/pub/bar'))
|
$expectedCollection->add('c_bar', (new Route('/sub/pub/bar'))
|
||||||
->setRequirements(array('id' => '\d+'))
|
->setRequirements(array('id' => '\d+'))
|
||||||
);
|
);
|
||||||
@ -106,8 +109,11 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
->setHost('host')
|
->setHost('host')
|
||||||
->setRequirements(array('id' => '\d+'))
|
->setRequirements(array('id' => '\d+'))
|
||||||
);
|
);
|
||||||
|
$expectedCollection->add('z_c_root', new Route('/zub/pub/'));
|
||||||
$expectedCollection->add('z_c_bar', new Route('/zub/pub/bar'));
|
$expectedCollection->add('z_c_bar', new Route('/zub/pub/bar'));
|
||||||
$expectedCollection->add('z_c_pub_buz', (new Route('/zub/pub/buz'))->setHost('host'));
|
$expectedCollection->add('z_c_pub_buz', (new Route('/zub/pub/buz'))->setHost('host'));
|
||||||
|
$expectedCollection->add('r_root', new Route('/bus'));
|
||||||
|
$expectedCollection->add('r_bar', new Route('/bus/bar/'));
|
||||||
$expectedCollection->add('ouf', (new Route('/ouf'))
|
$expectedCollection->add('ouf', (new Route('/ouf'))
|
||||||
->setSchemes(array('https'))
|
->setSchemes(array('https'))
|
||||||
->setMethods(array('GET'))
|
->setMethods(array('GET'))
|
||||||
@ -115,6 +121,7 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
);
|
);
|
||||||
|
|
||||||
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php')));
|
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php')));
|
||||||
|
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub_root.php')));
|
||||||
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
|
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
|
||||||
|
|
||||||
$this->assertEquals($expectedCollection, $routeCollection);
|
$this->assertEquals($expectedCollection, $routeCollection);
|
||||||
|
@ -411,4 +411,13 @@ class XmlFileLoaderTest extends TestCase
|
|||||||
$this->assertNotNull($routeCollection->get('api_app_blog'));
|
$this->assertNotNull($routeCollection->get('api_app_blog'));
|
||||||
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
|
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testImportRouteWithNoTrailingSlash()
|
||||||
|
{
|
||||||
|
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_no_trailing_slash')));
|
||||||
|
$routeCollection = $loader->load('routing.xml');
|
||||||
|
|
||||||
|
$this->assertEquals('/slash/', $routeCollection->get('a_app_homepage')->getPath());
|
||||||
|
$this->assertEquals('/no-slash', $routeCollection->get('b_app_homepage')->getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,6 @@ class YamlFileLoaderTest extends TestCase
|
|||||||
$this->assertCount(3, $routes);
|
$this->assertCount(3, $routes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testImportingRoutesFromDefinition()
|
public function testImportingRoutesFromDefinition()
|
||||||
{
|
{
|
||||||
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
|
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
|
||||||
@ -275,4 +274,13 @@ class YamlFileLoaderTest extends TestCase
|
|||||||
$this->assertEquals('DefaultController::defaultAction', $routes->get('home.nl')->getDefault('_controller'));
|
$this->assertEquals('DefaultController::defaultAction', $routes->get('home.nl')->getDefault('_controller'));
|
||||||
$this->assertEquals('DefaultController::defaultAction', $routes->get('not_localized')->getDefault('_controller'));
|
$this->assertEquals('DefaultController::defaultAction', $routes->get('not_localized')->getDefault('_controller'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testImportRouteWithNoTrailingSlash()
|
||||||
|
{
|
||||||
|
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_no_trailing_slash')));
|
||||||
|
$routeCollection = $loader->load('routing.yml');
|
||||||
|
|
||||||
|
$this->assertEquals('/slash/', $routeCollection->get('a_app_homepage')->getPath());
|
||||||
|
$this->assertEquals('/no-slash', $routeCollection->get('b_app_homepage')->getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user