PHP gives you $_GET, parse_str() and http_build_query() for query strings, and they all follow one old format from RFC 1866. There's no built-in way to handle RFC 3986 query strings, or to work with a query string that didn't come from the current request. This RFC from Máté Kocsis adds a Uri\QueryParams class, building on the URI extension added in PHP 8.5.
The RFC also points to app servers like FrankenPHP and Swoole, where one PHP process handles many requests. There, $_GET is global state that has to be reset every time. A QueryParams object is immutable, so that problem goes away.
Show me
Here are a few of the RFC's examples:
$params = Uri\QueryParams::parseRfc3986("foo=bar&foo=baz&qux=quux"); echo $params->getFirst("foo"); // bar echo $params->getLast("foo"); // baz $params = Uri\QueryParams::parseRfc3986("foo=bar"); $params = $params->append("baz", "qux"); echo $params->toRfc3986String(); // foo=bar&baz=qux
And moving off $_GET:
// Before $order = isset($_GET["order"]) ? (string) $_GET["order"] : null; // After $queryParams = Uri\QueryParams::parseRfc3986($_SERVER["QUERY_STRING"]); $order = $queryParams->getFirst("order");
What you get
- Three parsers:
parseRfc1866(),parseRfc3986()andparseWhatWgUrlSearchParams(), plusfromArray()and a constructor that starts with an empty list. The two RFC parsers throw aUri\InvalidQueryParamsExceptionfor input that isn't a valid query string, such as a stray#or a broken%sequence. The WHATWG parser accepts it, as browsers do. - Readers:
has(),hasValue(),getFirst(),getLast(),getAll()andcount(). - Modifiers:
append(),set(),delete(),deleteValue(),sortByBytes()andsortByUtf16(), each returning a new object. The two sorts differ only in how they compare names: byte by byte, or by UTF-16 code units as browsers do. - Output:
toRfc1866String(),toRfc3986String(),toWhatWgUrlString(),toList()andtoArray(). - Type handling that works like
http_build_query(). For example,truebecomes"1". AUri\QueryParamBuildingOptionsobject, passed to the constructor or a factory method, changes howtrue,falseandnullare stored. - Limits on length and number of parameters, with an exception when you go over.
For nested parameters like filter[status]=open, there are no special methods. You convert the list to a native PHP array with toArray(), change it with ordinary array code, and put it back with withArray(). An earlier draft had getArray(), appendArray() and setArray() for this, but the RFC dropped them for the simpler round trip.
It also fixes some $_GET surprises. Dots and spaces in names stay as they are instead of turning into underscores, and repeated names keep every value instead of just the last one.
What it means for existing code
Nothing breaks, and $_GET doesn't change. The RFC says superglobals should be phased out over time by offering better alternatives, not by changing them.
Where it stands
Máté Kocsis opened the internals discussion, and the RFC targets PHP 8.7. No vote has been scheduled yet. When it is, it will need a two-thirds majority to pass.