scratch – Blame information for rev
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
87 | office | 1 | <?php |
2 | |||
3 | namespace GuzzleHttp; |
||
4 | |||
5 | /** |
||
6 | * Manages query string variables and can aggregate them into a string |
||
7 | */ |
||
8 | class Query extends Collection |
||
9 | { |
||
10 | const RFC3986 = 'RFC3986'; |
||
11 | const RFC1738 = 'RFC1738'; |
||
12 | |||
13 | /** @var bool URL encode fields and values */ |
||
14 | private $encoding = self::RFC3986; |
||
15 | |||
16 | /** @var callable */ |
||
17 | private $aggregator; |
||
18 | |||
19 | /** |
||
20 | * Parse a query string into a Query object |
||
21 | * |
||
22 | * $urlEncoding is used to control how the query string is parsed and how |
||
23 | * it is ultimately serialized. The value can be set to one of the |
||
24 | * following: |
||
25 | * |
||
26 | * - true: (default) Parse query strings using RFC 3986 while still |
||
27 | * converting "+" to " ". |
||
28 | * - false: Disables URL decoding of the input string and URL encoding when |
||
29 | * the query string is serialized. |
||
30 | * - 'RFC3986': Use RFC 3986 URL encoding/decoding |
||
31 | * - 'RFC1738': Use RFC 1738 URL encoding/decoding |
||
32 | * |
||
33 | * @param string $query Query string to parse |
||
34 | * @param bool|string $urlEncoding Controls how the input string is decoded |
||
35 | * and encoded. |
||
36 | * @return self |
||
37 | */ |
||
38 | public static function fromString($query, $urlEncoding = true) |
||
39 | { |
||
40 | static $qp; |
||
41 | if (!$qp) { |
||
42 | $qp = new QueryParser(); |
||
43 | } |
||
44 | |||
45 | $q = new static(); |
||
46 | |||
47 | if ($urlEncoding !== true) { |
||
48 | $q->setEncodingType($urlEncoding); |
||
49 | } |
||
50 | |||
51 | $qp->parseInto($q, $query, $urlEncoding); |
||
52 | |||
53 | return $q; |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * Convert the query string parameters to a query string string |
||
58 | * |
||
59 | * @return string |
||
60 | */ |
||
61 | public function __toString() |
||
62 | { |
||
63 | if (!$this->data) { |
||
64 | return ''; |
||
65 | } |
||
66 | |||
67 | // The default aggregator is statically cached |
||
68 | static $defaultAggregator; |
||
69 | |||
70 | if (!$this->aggregator) { |
||
71 | if (!$defaultAggregator) { |
||
72 | $defaultAggregator = self::phpAggregator(); |
||
73 | } |
||
74 | $this->aggregator = $defaultAggregator; |
||
75 | } |
||
76 | |||
77 | $result = ''; |
||
78 | $aggregator = $this->aggregator; |
||
79 | |||
80 | foreach ($aggregator($this->data) as $key => $values) { |
||
81 | foreach ($values as $value) { |
||
82 | if ($result) { |
||
83 | $result .= '&'; |
||
84 | } |
||
85 | if ($this->encoding == self::RFC1738) { |
||
86 | $result .= urlencode($key); |
||
87 | if ($value !== null) { |
||
88 | $result .= '=' . urlencode($value); |
||
89 | } |
||
90 | } elseif ($this->encoding == self::RFC3986) { |
||
91 | $result .= rawurlencode($key); |
||
92 | if ($value !== null) { |
||
93 | $result .= '=' . rawurlencode($value); |
||
94 | } |
||
95 | } else { |
||
96 | $result .= $key; |
||
97 | if ($value !== null) { |
||
98 | $result .= '=' . $value; |
||
99 | } |
||
100 | } |
||
101 | } |
||
102 | } |
||
103 | |||
104 | return $result; |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Controls how multi-valued query string parameters are aggregated into a |
||
109 | * string. |
||
110 | * |
||
111 | * $query->setAggregator($query::duplicateAggregator()); |
||
112 | * |
||
113 | * @param callable $aggregator Callable used to convert a deeply nested |
||
114 | * array of query string variables into a flattened array of key value |
||
115 | * pairs. The callable accepts an array of query data and returns a |
||
116 | * flattened array of key value pairs where each value is an array of |
||
117 | * strings. |
||
118 | * |
||
119 | * @return self |
||
120 | */ |
||
121 | public function setAggregator(callable $aggregator) |
||
122 | { |
||
123 | $this->aggregator = $aggregator; |
||
124 | |||
125 | return $this; |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Specify how values are URL encoded |
||
130 | * |
||
131 | * @param string|bool $type One of 'RFC1738', 'RFC3986', or false to disable encoding |
||
132 | * |
||
133 | * @return self |
||
134 | * @throws \InvalidArgumentException |
||
135 | */ |
||
136 | public function setEncodingType($type) |
||
137 | { |
||
138 | if ($type === false || $type === self::RFC1738 || $type === self::RFC3986) { |
||
139 | $this->encoding = $type; |
||
140 | } else { |
||
141 | throw new \InvalidArgumentException('Invalid URL encoding type'); |
||
142 | } |
||
143 | |||
144 | return $this; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Query string aggregator that does not aggregate nested query string |
||
149 | * values and allows duplicates in the resulting array. |
||
150 | * |
||
151 | * Example: http://test.com?q=1&q=2 |
||
152 | * |
||
153 | * @return callable |
||
154 | */ |
||
155 | public static function duplicateAggregator() |
||
156 | { |
||
157 | return function (array $data) { |
||
158 | return self::walkQuery($data, '', function ($key, $prefix) { |
||
159 | return is_int($key) ? $prefix : "{$prefix}[{$key}]"; |
||
160 | }); |
||
161 | }; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Aggregates nested query string variables using the same technique as |
||
166 | * ``http_build_query()``. |
||
167 | * |
||
168 | * @param bool $numericIndices Pass false to not include numeric indices |
||
169 | * when multi-values query string parameters are present. |
||
170 | * |
||
171 | * @return callable |
||
172 | */ |
||
173 | public static function phpAggregator($numericIndices = true) |
||
174 | { |
||
175 | return function (array $data) use ($numericIndices) { |
||
176 | return self::walkQuery( |
||
177 | $data, |
||
178 | '', |
||
179 | function ($key, $prefix) use ($numericIndices) { |
||
180 | return !$numericIndices && is_int($key) |
||
181 | ? "{$prefix}[]" |
||
182 | : "{$prefix}[{$key}]"; |
||
183 | } |
||
184 | ); |
||
185 | }; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Easily create query aggregation functions by providing a key prefix |
||
190 | * function to this query string array walker. |
||
191 | * |
||
192 | * @param array $query Query string to walk |
||
193 | * @param string $keyPrefix Key prefix (start with '') |
||
194 | * @param callable $prefixer Function used to create a key prefix |
||
195 | * |
||
196 | * @return array |
||
197 | */ |
||
198 | public static function walkQuery(array $query, $keyPrefix, callable $prefixer) |
||
199 | { |
||
200 | $result = []; |
||
201 | foreach ($query as $key => $value) { |
||
202 | if ($keyPrefix) { |
||
203 | $key = $prefixer($key, $keyPrefix); |
||
204 | } |
||
205 | if (is_array($value)) { |
||
206 | $result += self::walkQuery($value, $key, $prefixer); |
||
207 | } elseif (isset($result[$key])) { |
||
208 | $result[$key][] = $value; |
||
209 | } else { |
||
210 | $result[$key] = array($value); |
||
211 | } |
||
212 | } |
||
213 | |||
214 | return $result; |
||
215 | } |
||
216 | } |