The Query
The library provides a League\Uri\Components\Query class to ease query string creation and manipulation. This URI component object exposes the package common API, but also provide specific methods to work with the URI query component.
Instantiation
<?php
public static Query::new(Stringable|string|null $value = null): self
public static Query::fromUri(): self
public static Query::fromRFC3986(Stringable|string $value, string $separator = '&'): self
public static Query::fromRFC1738(Stringable|string $value, string $separator = '&'): self
public static Query::fromFormData(Stringable|string $value, string $separator = '&'): self
newandfromRFC3986instantiates a query string encoded using RFC3986 query component rules;fromRFC1738instantiates a query string encoded using RFC1738 rules;fromFormDatainstantiates a query string encoded using application/x-www-form-urlencoded rules;
<?php
use League\Uri\Components\Query;
$query = Query::new('foo=bar&bar=baz%20bar%2A');
//or
$query = Query::fromRFC3986('foo=bar&bar=baz%20bar%2A', '&');
$query->get('bar'); // returns 'baz bar*'
$query = Query::fromRFC1738('foo=bar&bar=baz+bar%2A', '&');
$query->get('bar'); // returns 'baz bar*'
$query = Query::fromFormData('foo=bar&bar=baz+bar*', '&');
$query->get('bar'); // returns 'baz bar*'
Query separator
The query separator is essential to query manipulation. The Query object provides two (2) simple methods to interact with its separator:
public Query::getSeparator(string $separator): self
public Query::withSeparator(): string
Query::getSeparator returns the current separator attached to the Query object while Query::withSeparator returns a new Query object with an alternate string separator.
Query::withSeparator expects a single argument which is a string separator. If the separator is equal to = an exception will be thrown.
$query = Query::fromRFC3986('foo=bar&baz=toto');
$newQuery = $query->withSeparator('|');
$newQuery->__toString(); //return foo=bar|baz=toto
String Representations
In addition to the common methods from the package common API, the following methods are available.
RFC3986 Representation
The Query object can return the query encoded using the RFC3986 query component rules
$query = Query::fromRFC1738('foo=bar&bar=baz+bar%2A', '&');
$query->toRFC3986(); //returns 'foo=bar&bar=baz%20bar%2A'
$query->value(); //returns 'foo=bar&bar=baz%20bar%2A'
If the query is undefined, this method returns null.
RFC1738 Representation
The Query object returns the query encoded using the RFC1738 query component rules
$query = Query::fromRFC3986('foo=bar&bar=baz%20bar', '&');
$query->toRFC1738(); // returns 'foo=bar&bar=baz+bar'
$query->jsonSerialize(); //returns 'foo=bar&bar=baz+bar'
If the query is undefined, this method returns null.
FormData Representation
The Query object returns the query encoded using the application/x-www-form-urlencoded query component rules
$query = Query::fromRFC3986('foo=bar&bar=baz%20bar%2A', '&');
$query->toFormData(); // returns 'foo=bar&bar=baz+bar*'
$query->jsonSerialize(); //returns 'foo=bar&bar=baz+bar*'
If the query is undefined, this method returns null.
Modifying the Query
Query::merge returns a new Query object with its data merged.
<?php
public Query::merge($query): Query
This method expects a single argument which is a string
$query = Query::fromRFC3986('foo=bar&baz=toto');
$newQuery = $query->merge('foo=jane&r=stone');
$newQuery->__toString(); //return foo=jane&baz=toto&r=stone
// the 'foo' parameter was updated
// the 'r' parameter was added
$query = Query::fromRFC3986('foo=bar&baz=toto');
$newQuery = $query->merge('baz=&r');
$newQuery->__toString(); //return foo=bar&baz=&r
// the 'r' parameter was added without any value
// the 'baz' parameter was updated to an empty string and its = sign remains
Query::append returns a new Query object with its data append to it.
public Query::append($query): Query
This method expects a single argument which is a string, a scalar or an object with the __toString method.
$query = Query::fromRFC3986('foo=bar&john=doe');
$newQuery = $query->append('foo=baz');
$newQuery->__toString(); //return foo=bar&foo=baz&john=doe
// a new foo parameter is added
This method expects a single argument which is a string, a scalar or an object with the __toString method.
$query = Query::fromRFC3986('foo=bar&john=doe');
$newQuery = $query->prepend('foo=baz');
$newQuery->__toString(); //return foo=baz&foo=bar&john=doe
// a new foo parameter is added
Query::sort returns a Query object with its pairs sorted according to its keys. Sorting is done so
that parsing stayed unchanged before and after processing the query.
$query = Query::fromRFC3986('foo=bar&baz=toto&foo=toto');
$newQuery = $query->sort();
$newQuery->__toString(); //return baz=toto&foo=bar&foo=toto
PHP Data Transporter
Historically, the query string has been used as a data transport layer for PHP variables.
The Query class can be seen as a PHP Data Transporter layer and a public API was built
around this concept.
public static Query::fromVariable($params, string $separator = '&', QueryComposeMode $queryComposeMode = QueryComposeMode::Native): self
public Query::parameters(): array
public Query::parameter(string $name): mixed
public Query::hasParameter(string ...$name): bool
public Query::withoutNumericIndices(): self
public Query::withoutParameters(...string $offsets): self
public Query::mergeParameters(array $data): self
public Query::replaceParameter(string $name, mixed $value): self;
Instantiation
The fromVariable uses PHP own data structure to generate a query string à la http_build_query.
parse_str('foo=bar&bar=baz+bar', $params);
$query = Query::fromVariable($params, '|');
echo $query->value(); // returns 'foo=bar|bar=baz%20bar'
Accessing Parameters
If you already have an instantiated Query object you can return all the query string deserialized
arguments using the Query::parameters method:
$query_string = 'foo.bar=bar&foo_bar=baz';
parse_str($query_string, $out);
var_export($out);
// $out = ["foo_bar" => 'baz'];
$arr = Query::fromRFC3986($query_string)->parameters();
// $arr = ['foo.bar' => 'bar', 'foo_bar' => baz']];
As you can see from the previous example, the data is not mangle hence using the Query object you
get two distinctive properties instead of one, using PHP functions.
If you are only interested in a given argument, you can access it directly by supplyling the argument name as show below:
$query = Query::fromRFC3986('foo[]=bar&foo[]=y+olo&z=');
$query->parameter('foo'); //return ['bar', 'y+olo']
$query->parameter('gweta'); //return null
The method returns the value of a specific argument. If the argument does not exist it will return null.
The class can tell whether a parameter or a range of parameters are present using the Query::hasParameter method.
The method will take a variadic number of parameter names and will return true only if all the names are
present in the query parameter bag.
$query = Query::fromRFC3986('foo[]=bar&foo[]=y+olo&z=');
$query->hasParameter('gweta'); //return false
$query->hasParameter('foo', 'z'); //return true
$query->hasParameter('foo', 'gweta'); //return false
Modifying Parameters
Query::mergeParameters
This mergeParameters() method allows merging new parameters to your query using the http_build_query algorithm.
The method expects an array or an object and you can optionally provide a $prefix like with the http_build_query.
The new data will be merged with the current available PHP parameters.
$query = Query::fromRFC3986('foo[]=bar&foo[]=y+olo&z=');
$newQuery = $query->mergeParameters(['foo' => ['when' => 'today', 'where' => 'here']]);
$newQuery->params('foo'); //return ['when' => 'today', 'where' => 'here']
echo $newQuery->value(); //return 'foo%5Bwhen%5D=today&foo%5Bwhere%5D=here&z='
Query::replaceParameter
This replaceParameter() method allows changing a single parameter from your query using
the http_build_query algorithm. The method expects a parameter string as a name and its
new value. The data will replace the value in the current query.
$query = Query::fromRFC3986('foo[]=bar&foo[]=y+olo&z=');
$newQuery = $query->replaceParameter('foo', 1);
$newQuery->params('foo'); //return 1
echo $newQuery->value(); //return 'foo=1&z='
Query::withoutParameters
If you want to remove PHP’s variable from the query string you can use the Query::withoutParameters
method as shown below
$query = Query::fromRFC3986('foo[]=bar&foo[]=y+olo&z=');
$new_query = $query->withoutParameters('foo');
$new_query->params('foo'); //return null
echo $new_query->value(); //return 'z='
Query::withoutNumericIndices
If your query string is created with http_build_query or the Query::fromVariable named constructor chances are that numeric indices have been added by the method.
The Query::withoutNumericIndices removes any numeric index found in the query string as shown below:
$query = Query::fromVariable(['foo' => ['bar', 'baz']]);
echo $query->value(); //return 'foo[0]=bar&foo[1]=baz'
$new_query = $query->withoutNumericIndices();
echo $new_query->value(); //return 'foo[]=bar&foo[]=baz'
//of note both objects returns the same PHP's variables but differs regarding the pairs
$query->parameters(); //return ['foo' => ['bar', 'baz']]
$new_query->parameters(); //return ['foo' => ['bar', 'baz']]
Query as a Collection
To better support interoperability, the class also represents a query string as an ordered collection of key/value pairs, while also providing additional APIs to work with parameters that are parsed as lists using PHP’s bracket notation.
Instantiation
$pairs = QueryString::parse('foo=bar&bar=baz%20bar', '&', PHP_QUERY_RFC3986);
$query = Query::fromPairs($pairs, '|');
echo $query->value(); // returns 'foo=bar|bar=baz%20bar'
The $pairs input must an iterable which exposes the same structure as QueryString::parse return type structure.
Returns a new Query object from an array or a Traversable object.
$pairs: The submitted data must be anarrayor aTraversablekey/value structure similar to the result of Query::parse.$separator: The query string separator used for string representation, by default, equals to&;
$query = Query::fromPairs([
['foo', 'bar'],
['p', 'yolo'],
['z', ''],
]);
echo $query; //display 'foo=bar&p=yolo&z='
$query = Query::fromPairs([
['foo', 'bar'],
['p', null],
['z', ''],
]);
echo $query; //display 'foo=bar&p&z='
Accessing Pairs
A pair is an array containing 2 element, the first element represents the key is always a string.
The second elements and represent the pair value, it can be a string or null.
Using Pair Position
The Query::pair method returns the key/pair value at a given position in the query string.
The method supports negative offset. But if no value is found, an OutOfBoundException is triggered.
$query = Query::fromRFC3986('foo=bar&foo=BAZ&p=y+olo&z=');
$query->pair(2); // returns ['p', 'y+olo'];
$query->pair(-1); // returns ['z', ''];
$query->pair(42); // throws OutOfBoundException
To find a key/value pair specific position you can rely on 2 methods indexOf and indexOfValue.
Query::indexOf returns the key/value pair offset within the query. By default, if the key
appears multiple times, the method returns the offset of its first occurrence. You can
retrieve subsequent occurrences by specifying the desired index using the optional nth
parameter. The method accepts negative offset.
$query = Query::fromRFC3986('foo=bar&p=y+olo&z=&foo=jazz');
$query->indexOf('foo'); //returns 0
$query->indexOf('foo', 1); //returns 3
$query->indexOf('foo', -1); //returns 3
$query->indexOf('unknown', -1); //returns null
The same is true for Query::indexOfValue but its first argument is the pair value instead of the
pair key.
$query = Query::fromRFC3986('foo=bar&p=y+olo&z=bar&foo=jazz');
$query->indexOfValue('bar'); //returns 0
$query->indexOfValue('bar', 1); //returns 2
$query->indexOfValue('bar', -1); //returns 2
$query->indexOfValue('foo', -1); //returns null
If you are only interested in the pair key or value at a specific position you can use the
Query::keyAt() and Query::valueAt() methods to return the key or the value of the pair
at the specified offset. The methods support negative offset but if the offset does not exist
an OutOfBoundException is thrown.
$query = Query::fromRFC3986('foo=bar&foo=BAZ&p=y+olo&z=');
$query->valueAt(0); // return 'bar'
$query->keyAt(2); // return 'p'
$query->valueAt(42); // throws OutOfBoundException
Using Pair Key/Value
If you are only interested in a given pair you can access it directly using
the Query::get method as show below:
$query = Query::fromRFC3986('foo=bar&foo=BAZ&p=y+olo&z=');
$query->get('foo'); //return 'bar'
$query->get('gweta'); //return null
The method returns the first value of a specific pair key as explained in
the WHATWG documentation. If the key does not exist null will be returned.
Since version 7.7.0, Query::get() is an alias of Query::first(), Query::last() is the
complementary method which returns the last value of a specific pair key as explained.
If the key does not exist null will be returned. This value is more in line with what PHP
developer expects when using _GET or parse_str on non-array like value.
$query = Query::fromRFC3986('foo=bar&foo=BAZ&p=y+olo&z=');
$query->first('foo'); // return 'bar'
$query->last('foo'); // return 'BAZ'
parse_str('foo=bar&foo=BAZ&p=y+olo&z=', $arr);
$arr['foo']; // return 'BAZ'
Last but not least the Query::getAll method returns all the values associated with its
submitted $key.
$query = Query::fromRFC3986('foo=bar&foo=BAZ&p=y+olo&z=');
$query->getAll('foo'); //return ['bar', 'BAZ']
$query->getAll('gweta'); //return null
Because a query pair value can be null the Query::has method is used to remove the possible result ambiguity
when using Query::get, Query::first or Query::last
$query = Query::fromRFC3986('foo=bar&p&z=');
$query->get('foo'); //return 'bar'
$query->get('p'); //return null
$query->get('gweta'); //return null
$query->has('gweta'); //return false
$query->has('p'); //return true
Query::has can take a variable list of keys to validate that they are all present in the query.
$query = Query::fromRFC3986('foo=bar&p&z=');
$query->has('foo', 'p', 'z'); //return true
$query->has('foo', 'p', 'x'); //return false
If you are seeking the presence of a specific pair you may include the pair value in your search using Query::hasPair.
$query = Query::fromRFC3986('foo=bar&p&z=');
$query->hasPair('foo', 'bar'); //return true
$query->hasPair('foo', 'p'); //return false
$query->has('foo', 'p'); //return true
Collection methods
You can tell whether the collection is empty or not using the isEmpty() or isNotEmpty() methods.
The class implements PHP’s Countable and IteratorAggregate interfaces. This means that you can
count the number of pairs and use the foreach construct to iterate over them.
$query = new Query::fromRFC1738('foo=bar&p=y+olo&z=');
$query->isEmpty(); //return false
$query->isNotEmpty(); //return true
count($query); //return 3
foreach ($query as $pair) {
//first round
// $pair = ['foo', 'bar']
//second round
// $pair = ['p', 'y olo']
}
The Query::pairs method returns a Generator which enable iterating over each pair
where the offset represent the pair name while the value represent the pair value.
$query = Query::fromRFC3986('foo=bar&foo=BAZ&p=y+olo&z=');
foreach ($query->pairs() as $name => $value) {
//first round
// $name = 'foo' and $value = 'bar'
//second round
// $name = 'foo' and $value = 'BAZ'
}
Las but not least because keys can be repeated the class exposes the countDistinctKeys()
method which returns the number of distinct keys present in the query.
Modifying Pairs
Adding Pairs
The Query::withPair method will add a new key/value pair to the string:
- if no pair exists with the same name the pair is appended at the end of the pair list;
- if they are already presents, the value will be updated on the first occurrence and all the other pairs with the same key will be removed.
Query::new('foo=bar&foo=BAZ&p=y+olo&z=')->withPair('foo', 'new')->toString(); //return 'foo=new&p=y+olo&z='
Query::new('p=y+olo&z=')->withPair('foo', 'new')->toString(); //return p=y+olo&z=&foo=new
The Query::appendTo method will append a new pair to the key/pair list regardless of the presence
of other pairs sharing the same key.
Query::new('foo=bar&p=y+olo&z=')->appendTo('foo', 'new')->toString();
//return 'foo=bar&p=y+olo&z=&foo=new'
Removing Pairs
Query::withoutPairByKey returns a new Query object with deleted pairs according to their keys.
Query::withoutPairByValue does similar but delete the pairs according to their values. Last
but not least Query::withoutPairByKeyAndValue does the deletion depending on the presence of
the pair key and value.
Query::withoutPairByKey and Query::withoutPairByValue expect a variable list of key or value to
be removed as its sole arguments. Query::withoutPairByKeyAndValue on the other hand expect two (2)
parameter the pair’s key and value.
$query = Query::fromRFC3986('foo=bar&p=y+olo&z=');
echo $query->withoutPairByKey('foo', 'p')->toString(); //displays 'z='
echo $query->withoutPairByValue('bar')->toString(); //displays 'p=y+olo&z='
echo $query->withoutPairByKeyAndValue('p', 'y+olo')->toString(); //displays 'foo=bar&z='
Query::withoutEmptyPairs returns a new Query object with deleted empty pairs. A pair is considered empty if its key equals the empty string and its value is null.
$query = Query::fromRFC3986('&&=toto&&&&=&');
$newQuery = $query->withoutEmptyPairs();
echo $query; //displays '&&=toto&&&&=&'
echo $newQuery; //displays '=toto&='
Replacing Pairs
Query::replace replaces a key/value pair at a specific offset. The $offset parameter is zero-based: 0 refers to the first pair, 1 to the second, and so on.
Negative offsets are also supported and count from the end (-1 targets the last pair).
If the given offset does not exist, a ValueError is thrown.
If the new pair is identical to the existing one, the current instance is returned unchanged.
$query = Query::fromRFC3986('foo=bar&p=y+olo&z=&foo=jazz');
$query->replace(1, 'toto', 'foobar')->toString();
//returns 'foo=bar&toto=foobar&z=&foo=jazz'
Collection Methods
The class exposes the filter(), reduce() and map() methods with callbacks
expecting the pair structure. For filtering and mapping, the pair offset can optionally
be provided.
Handling List
PHP relies on the presence of brackets in parameter names to parse query string values into array. Since this notation is well established in the PHP community, this library provides a dedicated API to work with array parameters while still preserving the original key/value pairs.
Accessing List
The hasList() method tells whether a parameter exists as a structured list in the parsed
representation of the query string. (ie the key string should contains open and closes bracket as
per parse_str algorithm).
$query = Query::fromUri('http://example.com/?b=2&a[]=1&a[]=2&a[]=3');
$query->hasList('a'); // true
$query->hasList('b'); // false
The parameter a is written using bracket notation (a[]), after parsing using
PHP algorithm, an array is generated. On the otherhand the b parameter does not
exist as an array, it is a scalar value so hasList returns false.
The getList() method returns the values of a parameter only when that parameter is represented as a list
in the parsed form of the query string (i.e. using PHP bracket notation).
Scalar occurrences of the same parameter name are not included in the result.
$query = Query::fromUri('http://example.com/?b=2&a[foo]=1&a[]=2&a=not-present');
$query->getList('a'); // ['foo' => '1', '0' => '2']
$query->getAll('a'); // ['not-present']
$query->getAll('a[]'); // [0 => '2']
$query->getAll('b'); // [0 => '2']
$query->parameter('a'); // 'not-present' (the array got overwritten as per PHP's parse_str logic)
parse_str($query->toRfc1738(), $arr);
$arr['a']; // 'not-present' (the array got overwritten as per the function logic)
The example above shows how getList works and differs from getAll and parameter.
getList('a')returns only the values produced by bracketed parameters (a[foo],a[])getAll('a')returns only scalar occurrences ofaparameter('a')follows PHP’sparse_strresolution and returns the last scalar value
This illustrates how getList() provides structured access to list-style parameters while
preserving the underlying key/value pair model.
Modifying Lists
The Query::withList method adds or replaces a list parameter in the query string.
- If no list exists with the given name, the list is appended at the end of the pair collection.
- If one or more lists already exist with the same name, the first occurrence is replaced and all other lists with that name are removed.
Query::new('foo[]=bar&p=y+olo&z=&foo[]=BAZ')
->withList('foo', ['qux' => 'quux'])
->toString();
//return foo%5Bqux%5D=quux&p=y%2Bolo&z=
Query::new('p=y+olo&z=')
->withList('foo', ['qux' => 'quux'])
->toString();
//return p=y%2Bolo&z=&foo%5Bqux%5D=quux
The Query::appendList() method always appends a new list parameter, regardless of whether other lists with the same name already exist.
Query::new('foo[]=bar&p=y+olo&z=&foo[]=BAZ')
->appendList('foo', ['qux' => 'quux'])
->toString();
//return 'foo%5B%5D=bar&p=y%2Bolo&z=&foo%5B%5D=BAZ&foo%5Bqux%5D=quux'
Removing Lists
The Query::withoutList() method removes all list parameters with the given names from the query string.
Only parameters represented using bracket notation are affected. Scalar parameters with the same name are preserved.
Query::new('foo[]=bar&p=y+olo&z=&foo[qux]=quux&foo=scalar')
->withoutList('foo')
->toString();
// returns 'p=y%2Bolo&z=&foo=scalar'
The Query::withoutLists removes all list-based parameters from the query string.
Query::new('a=1&a[]=2&b=3&b[]=4')
->withoutLists()
->toString();
// returns a=1&b=3
The Query::onlyLists removes all scalar parameters and keeps only lists.
Query::new('a=1&a[]=2&b=3&b[]=4')
->onlyLists()
->toString();
// returns a%5B%5D=2&b%5B%5D=4