On Tue, Dec 16, 2025 at 7:14 PM Juris Evertovskis <juris@glaive.pro> wrote:
On 2025-12-16 09:53, ignace nyamagana butera wrote:
Since we will be dealing with arrays the following rules could be updated when parsing the string using PHP behaviour:
- “&a” should be converted to [‘a’ => null]
Hey Ignace,
In practice valueless arguments like
?debugare most often “flags” or “booleans” and their presence implies truthiness.Do you think it would be wrong or confusing to have it converted to
['debug' => true]?I’m worried that
['a' => null]would not be that handy since both$params['a']andisset($params['a'])would return falsy which would likely be opposite to the intended value.BR,
Juris
Hi Juris,
Do you think it would be wrong or confusing to have it converted to
['debug' => true]?
Yes IMHO it would be wrong because flag parameters or booleans are converted to [‘debug’ => 1]
The [‘debug’ => null] expresses the presence of the name pair and the absence of value associated with it.
Let’s see how it is currently done:
The WHATWG URL living standard does the following:
let url = new URL('[https://example.com?debug&foo=bar&debug=](https://example.com?debug&foo=bar&debug=)');
console.log(
url.searchParams.toString(), //returns debug=&foo=bar&debug='
);
the pair gets converted to [‘debug’ => ‘’]. The roundtrip does not conserve the query string as is but all key/pair (tuples) are present.
In PHP you have currently the following behaviour:
example 1
parse_str('debug&foo=bar&debug=', $params);
var_dump($params, http_build_query($params));
//$params ['debug' => '', 'foo' => 'bar']
//after roundtrip you get 'debug=&foo=bar'
example 2
parse_str('debug&foo=bar&debug=1', $params);
var_dump($params, http_build_query($params));
//$params ['debug' => '1', 'foo' => 'bar']
//after roundtrip you get 'debug=1&foo=bar'
So you lose data and the query data can be randomly sorted
parse_str convert the first debug into [‘debug’ => ‘’]
parse_str overwrites the value (This may be a security concern if you need to hash/validate your query string)
Since IMHO interoperability and security is important you should prefer an algorithm that preserves the original query.
The proposed solution is already in use for instance in League/Uri or in Guzzle
echo Uri::withQueryValues(Utils::uriFor('[https://example.com](https://example.com)'), [
'debug' => null,
'foo' => 'bar',
'baz' => '',
]), PHP_EOL;
// [https://example.com?debug&foo=bar&baz=](https://example.com?debug&foo=bar&baz=)
Because Guzzle uses an associative array, the debug variable can only appear once but there is a difference using null and the empty string.
This improves interoperability with other languages and you no longer have data loss or random query re-arrangement.
Last but not least, the Query objects proposed by Màté all expose:
- a
hasmethod which will always tell if the key is present regardless of its value an equivalent to array_key_exists. - provide a way to have the same parameter appear multiple times in the query string
So IMHO it is an improvement to also allow the distinction between null and the empty string so we can finally write in PHP
echo (new Uri\Rfc3986\Query())
->append('debug', null)
->append('foo', 'bar')
->append('debug', '')
->toRfc3986String();
// debug&foo=bar&baz=
Best regards,
Ignace