Issue 1:
Q
A

Bug report?
yes

Feature request?
no

BC Break report?
yes/no

RFC?
yes/no

Symfony version
3.3.2

We ran into a bug in the handling of periods in an API end point URL.

This query wouldn't work due to the period in faculty.position. This is the expected behavior since this is how it works in production.

$response = $this->get('/sample?faculty.position=Teacher');

This one should act similarly to the above but it actually handles it. I was thinking that both lines should be consistent on Symfony whether it handles periods or not.

$response = $this->get('/sample', ['faculty.position' => 'Teacher']);

We fixed this by using camelCase instead of using periods but I wanted to bring it up in case someone else encounters this.

The actual call to Symfony is:

$this->client()->request($method, $uri, $parameters);

which is within Client.php in the framework.

Issue 2:
Symfony version(s) affected: 4.1.9, 4.2.1

Description

Having defined a route without trailing slash in the path,

accessing the URL with a trailing slash automatically redirects to the URL without the trailing slash, but also replaces dots with underscores in the query string parameters names (if any).

How to reproduce

TL;DR:

Given my localhost app defines a route for method GET for path /foos (not /foos/)

When a client requests (GET) the URL http://localhost/foos/?nested.prop=bar

Then I expect the client is redirected to http://localhost/foos?nested.prop=bar

but actually the client is redirected to http://localhost/foos?nested_prop=bar

More details:

Given the following controller (note: no trailing slash after "foos"):

src/Controller/FooController.php

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class FooController
{
/**
* @Route("/foos", methods={"GET"})
*/
public function getFoos(Request $request)
{
/* Disclaimer: this is quick-and-dirty code. */

$html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>';
$html .= '<table border="1">';
$html .= '<tr><th>expression</th><th>value</th></tr>';
foreach ([
'$request->getUri()',
'$request->getSchemeAndHttpHost().$request->getRequestUri()',
'$request->getQueryString()',
'$request->server->get("QUERY_STRING")',
] as $expression) {
$value = eval("return $expression;");
[$expression, $value] = array_map('htmlspecialchars', [$expression, $value]);
$html .= "<tr><td><code>$expression</code></td><td><code>$value</code></td></tr>";
}
$html .= '</table>';
$html .= '</body></html>';

return new Response($html);
}
}

Then

http://localhost/foos?nested.prop=bar displays:

expression
value

$request->getUri()
http://localhost/foos?nested_prop=bar

$request->getSchemeAndHttpHost().$request->getRequestUri()
http://localhost/foos?nested.prop=bar

$request->getQueryString()
nested_prop=bar

$request->server->get("QUERY_STRING")
nested.prop=bar

(i.e. nested.prop is transformed to nested_prop but there are ways to get the original query string).

http://localhost/foos/?nested.prop=bar (note: trailing slash after "foos") redirects (301) to http://localhost/foos?nested_prop=bar with trailing slash removed but also dot replaced with underscore, which thus displays:

expression
value

$request->getUri()
http://localhost/foos?nested_prop=bar

$request->getSchemeAndHttpHost().$request->getRequestUri()
http://localhost/foos?nested_prop=bar

$request->getQueryString()
nested_prop=bar

$request->server->get("QUERY_STRING")
nested_prop=bar

i.e. we only have nested_prop, the original query string has been lost.

Additional context

With 3.4.20,

http://localhost/foos?nested.prop=bar displays:

expression
value

$request->getUri()
http://localhost/foos?nested.prop=bar

$request->getSchemeAndHttpHost().$request->getRequestUri()
http://localhost/foos?nested.prop=bar

$request->getQueryString()
nested.prop=bar

$request->server->get("QUERY_STRING")
nested.prop=bar

(i.e. nested.prop was preserved).

http://localhost/foos/?nested.prop=bar (note: trailing slash after "foos") gives a 404 error No route found for "GET /foos/" (no automatic redirect).

In all cases, $request->query->get("nested.prop") is null (not defined) (with 4.1, 4.2 and even 3.4), and we have to use $request->query->get("nested_prop") (or parse the original query string) to get "bar".

The real project uses API Platform, which auto-generates resource routes (without trailing slash) and parses the original query string to get nested filters (with dots). The calls with trailing slash are from automated REST clients.