Dealing with HTTP (Url) Query Strings in PHP

2022-06-02

There is a new package in town called query-string. It allows to create, access and manipulate query strings for HTTP requests in a very convenient way. Here's a quick overview of what you can do with it and also how it can be used via the url package.

The last months I started thinking about improving how you can change a URL's query string. It all started with this tweet as an answer to @heychazza's tweet about a nice way to build URLs in javascript.

Screenshot of a tweet by @chrolear saying: In PHP you can use my url package to get and set query params as array. I Could maybe also add a method to set/add a single param 🤔

Then last week someone added this github issue for the url package, and it got me thinking more about this. I liked the suggested API to get and set query params, but I found that it's not enough for more complex query strings. As query strings are also used in POST requests and sent in the request body, I now finally added a separate query-string package and also implemented it in the url package.

Implementation in the Url Package

First off: I set the required PHP version for the new package to 8.0 as the last 7.x version (7.4) is already in the final "security fixes only" phase. The url package currently still requires only 7.2. As I probably plan another BC break for v2 of the url package, for now I just added the query-string package as suggestion to the composer.json. You can manually install it, when you're already on PHP 8.x and want to use the advanced query string functionality.

When you've installed it via

composer require crwlr/query-string

the new queryString() method of the Url class returns an instance of the Query class shipped with the new package. Here's a quick usage example:

$url = Url::parse('https://www.example.com/listing?page[number]=3&page[size]=25');

$url->queryString()
    ->get('page')
    ->set('number', '4');

var_dump($url->__toString());

// string(68) "https://www.example.com/listing?page%5Bnumber%5D=4&page%5Bsize%5D=25"

Standalone Usage

If you want to parse query strings standalone, not in the URL context, you can create an instance of the Query class from string or from array:

$query = Query::fromString('foo=bar&baz=quz');

$query = Query::fromArray(['foo' => 'bar', 'baz' => 'quz']);

Access

Here a quick example of different ways how to access query string params:

$fooValue = Query::fromString('foo=bar&baz=quz')->get('foo'); // string(3) "bar"

When the requested key is an array, the get() method returns another (child) Query instance that you can query further:

$fooBazValue = Query::fromString('foo[bar]=1&foo[baz]=2&foo[quz]=3')
    ->get('foo')
    ->get('baz'); // string(1) "2"

You can check if a certain key exists in the query:

$query = Query::fromString('foo=1&bar=2');

$query->has('bar'); // bool(true)

$query->has('baz'); // bool(false)

You can get the first or last element of an indexed array:

$query = Query::fromString('foo[]=1&foo[]=2&foo[]=3');

$query->first('foo'); // string(1) "1"

$query->last('foo');  // string(1) "3"

You can check if the value for a certain key is an array of a scalar value:

$query = Query::fromString('foo[]=1&foo[]=2&bar=3');

$query->isArray('foo'); // bool(true)

$query->isScalar('foo'); // bool(false)

$query->isArray('bar'); // bool(false)

$query->isScalar('bar'); // bool(true)

And of course you can then convert the query to a string or to an array again:

$query = Query::fromString('foo=bar&baz=quz');

$queryArray = $query->toArray();

// array(2) {
//   ["foo"]=>
//   string(3) "bar"
//   ["baz"]=>
//   string(3) "quz"
// }

$query = Query::fromArray(['foo' => 'bar', 'baz' => 'quz']);

$queryString = $query->toString(); // string(15) "foo=bar&baz=quz"

Manipulation

You can set a certain key:

$query = Query::fromString('foo=bar')->set('baz', 'quz');

// string(15) "foo=bar&baz=quz"

Also to an array:

$query = Query::fromString('foo=1&bar=2')
    ->set('baz', ['3', '4']);

// string(29) "foo=1&bar=2&baz[0]=3&baz[1]=4"

You can also append values to an existing array:

$query = Query::fromString('foo[]=1&foo[]=2')
    ->appendTo('foo', '3');

// string(26) "foo[0]=1&foo[1]=2&foo[2]=3"

$query = Query::fromString('foo[bar]=1&foo[baz]=2')
    ->appendTo('foo', ['quz' => '3']);

// string(32) "foo[bar]=1&foo[baz]=2&foo[quz]=3"

Remove keys or values from keys:

$query = Query::fromString('foo[]=1&foo[]=2&bar=3&baz=4')
    ->remove('foo');

// string(11) "bar=3&baz=4"

$query = Query::fromString('foo[]=1&foo[]=2&foo[]=3&foo[]=2')
    ->removeValueFrom('foo', '2');

// string(17) "foo[0]=1&foo[1]=3"

And you can filter or map queries with callback functions:

$query = Query::fromString('no1=12&no2=7&no3=23&no4=9&no5=10')
    ->filter(function ($value, $key) {
        return (int) $value >= 10;
    });

// string(20) "no1=12&no3=23&no5=10"

$query = Query::fromString('foo=1&bar=2&baz=3&quz=4')
    ->map(function ($value) {
        return (int) $value + 3;
    });

// string(23) "foo=4&bar=5&baz=6&quz=7"

For more details have a look at the documentation. If you're having any question or issues, don't be shy and reach out on twitter or github.