5
* Parses query strings into a Query object.
7
* While parsing, the parser will attempt to determine the most appropriate
8
* query string aggregator to use when serializing the parsed query string
9
* object back into a string. The hope is that parsing then serializing a
10
* query string should be a lossless operation.
12
* @internal Use Query::fromString()
17
private $numericIndices;
20
* Parse a query string into a Query object.
22
* @param Query $query Query object to populate
23
* @param string $str Query string to parse
24
* @param bool|string $urlEncoding How the query string is encoded
26
public function parseInto(Query $query, $str, $urlEncoding = true)
33
$this->duplicates = false;
34
$this->numericIndices = true;
35
$decoder = self::getDecoder($urlEncoding);
37
foreach (explode('&', $str) as $kvp) {
39
$parts = explode('=', $kvp, 2);
40
$key = $decoder($parts[0]);
41
$value = isset($parts[1]) ? $decoder($parts[1]) : null;
43
// Special handling needs to be taken for PHP nested array syntax
44
if (strpos($key, '[') !== false) {
45
$this->parsePhpValue($key, $value, $result);
49
if (!isset($result[$key])) {
50
$result[$key] = $value;
52
$this->duplicates = true;
53
if (!is_array($result[$key])) {
54
$result[$key] = [$result[$key]];
56
$result[$key][] = $value;
60
$query->replace($result);
62
if (!$this->numericIndices) {
63
$query->setAggregator(Query::phpAggregator(false));
64
} elseif ($this->duplicates) {
65
$query->setAggregator(Query::duplicateAggregator());
70
* Returns a callable that is used to URL decode query keys and values.
72
* @param string|bool $type One of true, false, RFC3986, and RFC1738
74
* @return callable|string
76
private static function getDecoder($type)
79
return function ($value) {
80
return rawurldecode(str_replace('+', ' ', $value));
82
} elseif ($type == Query::RFC3986) {
83
return 'rawurldecode';
84
} elseif ($type == Query::RFC1738) {
87
return function ($str) { return $str; };
92
* Parses a PHP style key value pair.
94
* @param string $key Key to parse (e.g., "foo[a][b]")
95
* @param string|null $value Value to set
96
* @param array $result Result to modify by reference
98
private function parsePhpValue($key, $value, array &$result)
103
for ($i = 0, $t = strlen($key); $i < $t; $i++) {
107
$this->prepareNode($node, $keyBuffer);
108
$node =& $node[$keyBuffer];
113
$k = $this->cleanKey($node, $keyBuffer);
114
$this->prepareNode($node, $k);
119
$keyBuffer .= $key[$i];
125
$this->duplicates = true;
133
* Prepares a value in the array at the given key.
135
* If the key already exists, the key value is converted into an array.
137
* @param array $node Result node to modify
138
* @param string $key Key to add or modify in the node
140
private function prepareNode(&$node, $key)
142
if (!isset($node[$key])) {
144
} elseif (!is_array($node[$key])) {
145
$node[$key] = [$node[$key]];
150
* Returns the appropriate key based on the node and key.
152
private function cleanKey($node, $key)
155
$key = $node ? (string) count($node) : 0;
156
// Found a [] key, so track this to ensure that we disable numeric
157
// indexing of keys in the resolved query aggregator.
158
$this->numericIndices = false;