<table role="table">
<thead>
<tr>
<th>Q</th>
<th>A</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bug report?</td>
<td>yes</td>
</tr>
<tr>
<td>Feature request?</td>
<td>no</td>
</tr>
<tr>
<td>BC Break report?</td>
<td>yes/no</td>
</tr>
<tr>
<td>RFC?</td>
<td>yes/no</td>
</tr>
<tr>
<td>Symfony version</td>
<td>3.3.2</td>
</tr>
</tbody>
</table>
<p dir="auto">We ran into a bug in the handling of periods in an API end point URL.</p>
<p dir="auto">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.<br>
<code class="notranslate">$response = $this-&gt;get('/sample?faculty.position=Teacher');</code></p>
<p dir="auto">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.<br>
<code class="notranslate">$response = $this-&gt;get('/sample', ['faculty.position' =&gt; 'Teacher']);</code></p>
<p dir="auto">We fixed this by using camelCase instead of using periods but I wanted to bring it up in case someone else encounters this.</p>
<p dir="auto">The actual call to Symfony is:<br>
<code class="notranslate">$this-&gt;client()-&gt;request($method, $uri, $parameters);</code><br>
which is within Client.php in the framework.</p>
<p dir="auto"><strong>Symfony version(s) affected</strong>: 4.1.9, 4.2.1</p>
<p dir="auto"><strong>Description</strong></p>
<p dir="auto">Having defined a route without trailing slash in the path,<br>
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).</p>
<p dir="auto"><strong>How to reproduce</strong></p>
<h5 dir="auto">TL;DR:</h5>
<p dir="auto">Given my <code class="notranslate">localhost</code> app defines a route for method GET for path <code class="notranslate">/foos</code> (not <code class="notranslate">/foos/</code>)<br>
When a client requests (GET) the URL <code class="notranslate">http://localhost/foos/?nested.prop=bar</code><br>
Then I expect the client is redirected to <code class="notranslate">http://localhost/foos?nested.prop=bar</code><br>
    but actually the client is redirected to <code class="notranslate">http://localhost/foos?nested_prop=bar</code></p>
<h5 dir="auto">More details:</h5>
<p dir="auto">Given the following controller (note: no trailing slash after "foos"):</p>
<details>
<summary>src/Controller/FooController.php</summary>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

namespace App\Controller;

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

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

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

        return new Response($html);
    }
}"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-k">namespace</span> <span class="pl-v">App</span>\<span class="pl-v">Controller</span>;

<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">HttpFoundation</span>\<span class="pl-v">Request</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">HttpFoundation</span>\<span class="pl-v">Response</span>;
<span class="pl-k">use</span> <span class="pl-v">Symfony</span>\<span class="pl-v">Component</span>\<span class="pl-v">Routing</span>\<span class="pl-v">Annotation</span>\<span class="pl-v">Route</span>;

<span class="pl-k">class</span> <span class="pl-v">FooController</span>
{
    <span class="pl-c">/**</span>
<span class="pl-c">     * @Route("/foos", methods={"GET"})</span>
<span class="pl-c">     */</span>
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">getFoos</span>(<span class="pl-smi"><span class="pl-smi">Request</span></span> <span class="pl-s1"><span class="pl-c1">$</span>request</span>)
    {
        <span class="pl-c">/* Disclaimer: this is quick-and-dirty code. */</span>

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

        <span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-v">Response</span>(<span class="pl-s1"><span class="pl-c1">$</span>html</span>);
    }
}</pre></div>
</details>
<p dir="auto">Then</p>
<ul dir="auto">
<li>
<p dir="auto"><a href="http://localhost/foos?nested.prop=bar" rel="nofollow">http://localhost/foos?nested.prop=bar</a> displays:</p>
<table role="table">
<thead>
<tr>
<th>expression</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="notranslate">$request-&gt;getUri()</code></td>
<td><code class="notranslate">http://localhost/foos?nested_prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;getSchemeAndHttpHost().$request-&gt;getRequestUri()</code></td>
<td><code class="notranslate">http://localhost/foos?nested.prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;getQueryString()</code></td>
<td><code class="notranslate">nested_prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;server-&gt;get("QUERY_STRING")</code></td>
<td><code class="notranslate">nested.prop=bar</code></td>
</tr>
</tbody>
</table>
<p dir="auto">(i.e. <code class="notranslate">nested.prop</code> is transformed to <code class="notranslate">nested_prop</code> but there are ways to get the original query string).</p>
</li>
<li>
<p dir="auto"><a href="http://localhost/foos/?nested.prop=bar" rel="nofollow">http://localhost/foos/?nested.prop=bar</a> (note: trailing slash after "foos") redirects (301) to <a href="http://localhost/foos?nested_prop=bar" rel="nofollow">http://localhost/foos?nested_prop=bar</a> with trailing slash removed <em>but also dot replaced with underscore</em>, which thus displays:</p>
<table role="table">
<thead>
<tr>
<th>expression</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="notranslate">$request-&gt;getUri()</code></td>
<td><code class="notranslate">http://localhost/foos?nested_prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;getSchemeAndHttpHost().$request-&gt;getRequestUri()</code></td>
<td><code class="notranslate">http://localhost/foos?nested_prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;getQueryString()</code></td>
<td><code class="notranslate">nested_prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;server-&gt;get("QUERY_STRING")</code></td>
<td><code class="notranslate">nested_prop=bar</code></td>
</tr>
</tbody>
</table>
<p dir="auto">i.e. we only have <code class="notranslate">nested_prop</code>, the original query string has been lost.</p>
</li>
</ul>
<p dir="auto"><strong>Additional context</strong></p>
<p dir="auto">With 3.4.20,</p>
<ul dir="auto">
<li>
<p dir="auto"><a href="http://localhost/foos?nested.prop=bar" rel="nofollow">http://localhost/foos?nested.prop=bar</a> displays:</p>
<table role="table">
<thead>
<tr>
<th>expression</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="notranslate">$request-&gt;getUri()</code></td>
<td><code class="notranslate">http://localhost/foos?nested.prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;getSchemeAndHttpHost().$request-&gt;getRequestUri()</code></td>
<td><code class="notranslate">http://localhost/foos?nested.prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;getQueryString()</code></td>
<td><code class="notranslate">nested.prop=bar</code></td>
</tr>
<tr>
<td><code class="notranslate">$request-&gt;server-&gt;get("QUERY_STRING")</code></td>
<td><code class="notranslate">nested.prop=bar</code></td>
</tr>
</tbody>
</table>
<p dir="auto">(i.e. <code class="notranslate">nested.prop</code> was preserved).</p>
</li>
<li>
<p dir="auto"><a href="http://localhost/foos/?nested.prop=bar" rel="nofollow">http://localhost/foos/?nested.prop=bar</a> (note: trailing slash after "foos") gives a 404 error <code class="notranslate">No route found for "GET /foos/"</code> (no automatic redirect).</p>
</li>
</ul>
<p dir="auto">In all cases, <code class="notranslate">$request-&gt;query-&gt;get("nested.prop")</code> is <code class="notranslate">null</code> (not defined) (with 4.1, 4.2 and even 3.4), and we have to use <code class="notranslate">$request-&gt;query-&gt;get("nested_prop")</code> (or parse the original query string) to get <code class="notranslate">"bar"</code>.</p>
<p dir="auto">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.</p>