Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7262c59

Browse filesBrowse files
committed
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 ------- 5a98515 [Routing] allow no-slash root on imported routes
2 parents 07a2f6c + 5a98515 commit 7262c59
Copy full SHA for 7262c59

File tree

12 files changed

+109
-16
lines changed
Filter options

12 files changed

+109
-16
lines changed

‎src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php
+24-9Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Routing\Loader\Configurator;
1313

14+
use Symfony\Component\Routing\Route;
1415
use Symfony\Component\Routing\RouteCollection;
1516

1617
/**
@@ -40,16 +41,18 @@ public function __destruct()
4041
*
4142
* @return $this
4243
*/
43-
final public function prefix($prefix, string $namePrefix = '')
44+
final public function prefix($prefix, bool $trailingSlashOnRoot = true)
4445
{
45-
if ('' !== $namePrefix) {
46-
$this->route->addNamePrefix($namePrefix);
47-
}
48-
if (!$prefix) {
49-
return $this;
50-
}
5146
if (!\is_array($prefix)) {
5247
$this->route->addPrefix($prefix);
48+
if (!$trailingSlashOnRoot) {
49+
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
50+
foreach ($this->route->all() as $route) {
51+
if ($route->getPath() === $rootPath) {
52+
$route->setPath(rtrim($rootPath, '/'));
53+
}
54+
}
55+
}
5356
} else {
5457
foreach ($prefix as $locale => $localePrefix) {
5558
$prefix[$locale] = trim(trim($localePrefix), '/');
@@ -61,18 +64,30 @@ final public function prefix($prefix, string $namePrefix = '')
6164
$localizedRoute = clone $route;
6265
$localizedRoute->setDefault('_locale', $locale);
6366
$localizedRoute->setDefault('_canonical_route', $name);
64-
$localizedRoute->setPath($localePrefix.$route->getPath());
67+
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
6568
$this->route->add($name.'.'.$locale, $localizedRoute);
6669
}
6770
} elseif (!isset($prefix[$locale])) {
6871
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
6972
} else {
70-
$route->setPath($prefix[$locale].$route->getPath());
73+
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
7174
$this->route->add($name, $route);
7275
}
7376
}
7477
}
7578

7679
return $this;
7780
}
81+
82+
/**
83+
* Sets the prefix to add to the name of all child routes.
84+
*
85+
* @return $this
86+
*/
87+
final public function namePrefix(string $namePrefix)
88+
{
89+
$this->route->addNamePrefix($namePrefix);
90+
91+
return $this;
92+
}
7893
}

‎src/Symfony/Component/Routing/Loader/XmlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Loader/XmlFileLoader.php
+11-2Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
158158
$host = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
159159
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
160160
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
161+
$trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true;
161162

162163
list($defaults, $requirements, $options, $condition, /* $paths */, $prefixes) = $this->parseConfigs($node, $path);
163164

@@ -172,6 +173,14 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
172173

173174
if ('' !== $prefix || !$prefixes) {
174175
$subCollection->addPrefix($prefix);
176+
if (!$trailingSlashOnRoot) {
177+
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
178+
foreach ($subCollection->all() as $route) {
179+
if ($route->getPath() === $rootPath) {
180+
$route->setPath(rtrim($rootPath, '/'));
181+
}
182+
}
183+
}
175184
} else {
176185
foreach ($prefixes as $locale => $localePrefix) {
177186
$prefixes[$locale] = trim(trim($localePrefix), '/');
@@ -181,15 +190,15 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
181190
$subCollection->remove($name);
182191
foreach ($prefixes as $locale => $localePrefix) {
183192
$localizedRoute = clone $route;
184-
$localizedRoute->setPath($localePrefix.$route->getPath());
193+
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
185194
$localizedRoute->setDefault('_locale', $locale);
186195
$localizedRoute->setDefault('_canonical_route', $name);
187196
$subCollection->add($name.'.'.$locale, $localizedRoute);
188197
}
189198
} elseif (!isset($prefixes[$locale])) {
190199
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $path));
191200
} else {
192-
$route->setPath($prefixes[$locale].$route->getPath());
201+
$route->setPath($prefixes[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
193202
$subCollection->add($name, $route);
194203
}
195204
}

‎src/Symfony/Component/Routing/Loader/YamlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Loader/YamlFileLoader.php
+12-3Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
class YamlFileLoader extends FileLoader
2929
{
3030
private static $availableKeys = array(
31-
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix',
31+
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root',
3232
);
3333
private $yamlParser;
3434

@@ -155,6 +155,7 @@ protected function parseImport(RouteCollection $collection, array $config, $path
155155
$condition = isset($config['condition']) ? $config['condition'] : null;
156156
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
157157
$methods = isset($config['methods']) ? $config['methods'] : null;
158+
$trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true;
158159

159160
if (isset($config['controller'])) {
160161
$defaults['_controller'] = $config['controller'];
@@ -167,6 +168,14 @@ protected function parseImport(RouteCollection $collection, array $config, $path
167168

168169
if (!\is_array($prefix)) {
169170
$subCollection->addPrefix($prefix);
171+
if (!$trailingSlashOnRoot) {
172+
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
173+
foreach ($subCollection->all() as $route) {
174+
if ($route->getPath() === $rootPath) {
175+
$route->setPath(rtrim($rootPath, '/'));
176+
}
177+
}
178+
}
170179
} else {
171180
foreach ($prefix as $locale => $localePrefix) {
172181
$prefix[$locale] = trim(trim($localePrefix), '/');
@@ -178,13 +187,13 @@ protected function parseImport(RouteCollection $collection, array $config, $path
178187
$localizedRoute = clone $route;
179188
$localizedRoute->setDefault('_locale', $locale);
180189
$localizedRoute->setDefault('_canonical_route', $name);
181-
$localizedRoute->setPath($localePrefix.$route->getPath());
190+
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
182191
$subCollection->add($name.'.'.$locale, $localizedRoute);
183192
}
184193
} elseif (!isset($prefix[$locale])) {
185194
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $file));
186195
} else {
187-
$route->setPath($prefix[$locale].$route->getPath());
196+
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
188197
$subCollection->add($name, $route);
189198
}
190199
}

‎src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
<xsd:attribute name="schemes" type="xsd:string" />
6868
<xsd:attribute name="methods" type="xsd:string" />
6969
<xsd:attribute name="controller" type="xsd:string" />
70+
<xsd:attribute name="trailing-slash-on-root" type="xsd:boolean" />
7071
</xsd:complexType>
7172

7273
<xsd:complexType name="default" mixed="true">
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<routes xmlns="http://symfony.com/schema/routing"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/routing
5+
http://symfony.com/schema/routing/routing-1.0.xsd">
6+
7+
<import resource="../controller/routing.xml" prefix="/slash" name-prefix="a_" />
8+
<import resource="../controller/routing.xml" prefix="/no-slash" name-prefix="b_" trailing-slash-on-root="false" />
9+
10+
</routes>
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
app:
2+
resource: ../controller/routing.yml
3+
name_prefix: a_
4+
prefix: /slash
5+
6+
api:
7+
resource: ../controller/routing.yml
8+
name_prefix: b_
9+
prefix: /no-slash
10+
trailing_slash_on_root: false

‎src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
->requirements(array('id' => '\d+'));
1717

1818
$routes->import('php_dsl_sub.php')
19-
->prefix('/zub', 'z_');
19+
->namePrefix('z_')
20+
->prefix('/zub');
21+
22+
$routes->import('php_dsl_sub_root.php')
23+
->prefix('/bus', false);
2024

2125
$routes->add('ouf', '/ouf')
2226
->schemes(array('https'))

‎src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
$add = $routes->collection('c_')
77
->prefix('pub');
88

9+
$add('root', '/');
910
$add('bar', '/bar');
1011

1112
$add->collection('pub_')
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Routing\Loader\Configurator;
4+
5+
return function (RoutingConfigurator $routes) {
6+
$add = $routes->collection('r_');
7+
8+
$add('root', '/');
9+
$add('bar', '/bar/');
10+
};

‎src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,29 @@ public function testRoutingConfigurator()
9999
$expectedCollection->add('buz', (new Route('/zub'))
100100
->setDefaults(array('_controller' => 'foo:act'))
101101
);
102+
$expectedCollection->add('c_root', (new Route('/sub/pub/'))
103+
->setRequirements(array('id' => '\d+'))
104+
);
102105
$expectedCollection->add('c_bar', (new Route('/sub/pub/bar'))
103106
->setRequirements(array('id' => '\d+'))
104107
);
105108
$expectedCollection->add('c_pub_buz', (new Route('/sub/pub/buz'))
106109
->setHost('host')
107110
->setRequirements(array('id' => '\d+'))
108111
);
112+
$expectedCollection->add('z_c_root', new Route('/zub/pub/'));
109113
$expectedCollection->add('z_c_bar', new Route('/zub/pub/bar'));
110114
$expectedCollection->add('z_c_pub_buz', (new Route('/zub/pub/buz'))->setHost('host'));
115+
$expectedCollection->add('r_root', new Route('/bus'));
116+
$expectedCollection->add('r_bar', new Route('/bus/bar/'));
111117
$expectedCollection->add('ouf', (new Route('/ouf'))
112118
->setSchemes(array('https'))
113119
->setMethods(array('GET'))
114120
->setDefaults(array('id' => 0))
115121
);
116122

117123
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php')));
124+
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub_root.php')));
118125
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
119126

120127
$this->assertEquals($expectedCollection, $routeCollection);

‎src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,13 @@ public function testImportRouteWithNamePrefix()
411411
$this->assertNotNull($routeCollection->get('api_app_blog'));
412412
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
413413
}
414+
415+
public function testImportRouteWithNoTrailingSlash()
416+
{
417+
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_no_trailing_slash')));
418+
$routeCollection = $loader->load('routing.xml');
419+
420+
$this->assertEquals('/slash/', $routeCollection->get('a_app_homepage')->getPath());
421+
$this->assertEquals('/no-slash', $routeCollection->get('b_app_homepage')->getPath());
422+
}
414423
}

‎src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
+9-1Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ public function testLoadingLocalizedRoute()
209209
$this->assertCount(3, $routes);
210210
}
211211

212-
213212
public function testImportingRoutesFromDefinition()
214213
{
215214
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
@@ -275,4 +274,13 @@ public function testImportingWithControllerDefault()
275274
$this->assertEquals('DefaultController::defaultAction', $routes->get('home.nl')->getDefault('_controller'));
276275
$this->assertEquals('DefaultController::defaultAction', $routes->get('not_localized')->getDefault('_controller'));
277276
}
277+
278+
public function testImportRouteWithNoTrailingSlash()
279+
{
280+
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_no_trailing_slash')));
281+
$routeCollection = $loader->load('routing.yml');
282+
283+
$this->assertEquals('/slash/', $routeCollection->get('a_app_homepage')->getPath());
284+
$this->assertEquals('/no-slash', $routeCollection->get('b_app_homepage')->getPath());
285+
}
278286
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.