Issue 1:
Hey,

I found something weird... I have a routing system with a modular structure, inside my general routing.yml there's an api_routing.yml and, inside it, there's one .yml for every 'module' of my application, those are:

benefits_routing.yml

entities_routing.yml

user_routing.yml

Focusing on benefits, the general routing.yml take all /api routes to go to api_routing.yml, that takes all /benefits routes to go to benefits_routing.yml. Thus, if routes inside my benefits_routing.yml are:

benefits_routing.yml

api_list_benefits:
path: /
defaults: {_controller: "AppBundle:Benefits:list" }
methods: [GET]
api_new_benefits:
path: /
defaults: {_controller: "AppBundle:Benefits:new" }
methods: [POST]

Then, if I make a POST request to http://domain/api/benefits, the router should match to the second route, right? Nope.

When I post to '/api/benefits' router throw a 405. Though, if I post to '/api/benefits/' it works. Weird, huh? In addition, both '/api/benefits' or '/api/benefits/' with GET method works...

I had a terrible hard time with this trouble and opened a question in stack overflow:

http://stackoverflow.com/questions/37834923/symfony3-routing-in-production

And then I started playing with Symfony code. Suprise! In my generated appProdUrlMatcher.php I found the following. For the first (GET) route, I could see:

if (rtrim($pathinfo, '/') === '/api/entities') {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
//bla bla
}
}

Perfect, that's why GET works! It's "rtrimming" the route making the final slash mean nothing.

Although, with the POST one:

if ($pathinfo === '/api/entities/') {
if ($this->context->getMethod() != 'POST') {
//bla bla
}
}

Wait... what? Why isn't it rtrimming my route?

Okey... Once found this, the solution was clear: go to the place where it's generated and fix it. Several minutes later I found it:

/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php::compileRoute

...
// GET and HEAD are equivalent
if (in_array('GET', $methods) && !in_array('HEAD', $methods)) {
$methods[] = 'HEAD';
}
....
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
....
if ($supportsTrailingSlash && substr($m['url'], -1) === '/') {
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
$hasTrailingSlash = true;
} else {
$conditions[] = sprintf('$pathinfo === %s', var_export(str_replace('\\', '', $m['url']), true));
}
....

GET falls in the first condition, where it adds the rtrim, but POST does not.

I hope I have explained well the matter, it was not easy...

Question: Is this deliberated? If yes (I guess so), why? How can we fix then the final slash problem?

Best and many thanks,

Ignacio

Edit:

I just realized that, if the request "supports trailing slash" it also adds:

if (substr($pathinfo, -1) !== '/') {
return $this->redirect($pathinfo.'/', 'api_list_entities');
}

Which actually breaks in POST, because the redirect is always GET, I guess that's why the condition of GET or HEAD...

How to fix this? I have a patch for the PhpMatcherDumper.php::compileRoute function, but I guess that's not the way to proceed.

Thanks!

Issue 2:
There is currently a known limitation when using route imports that makes it impossible to to have a root route / be imported under a prefix without a trailing slash. See #4322 and silexphp/Silex#149

I propose to solve this problem in a generic way by offering an option when importing routes to remove or add trailing slashes (which defaults to null, meaning not modifying anything):

AcmeBusinessBundle_client:
resource: "@AcmeBusinessBundle/Resources/config/routing/client.yml"
prefix: /clients
trailing_slashes: true|false|null

This way, the users can unify the path on import the way they want.

So for example

acme_business_client_list:
path: /
defaults: { _controller: AcmeBusinessBundle:Client:list }

imported with trailing_slashes: false under prefix: /clients will result in path /clients instead of /clients/.

The other case with trailing_slashes: true is helpful when you import third party routes without trailing slash but you decided that you app should use training slashes everywhere consistently.