1: <?php
2:
3: namespace Guzzle\Http;
4:
5: use Guzzle\Common\Collection;
6: use Guzzle\Http\QueryAggregator\QueryAggregatorInterface;
7: use Guzzle\Http\QueryAggregator\PhpAggregator;
8:
9: /**
10: * Query string object to handle managing query string parameters and aggregating those parameters together as a string.
11: */
12: class QueryString extends Collection
13: {
14: /**
15: * @var string Used to URL encode with rawurlencode
16: */
17: const RFC_3986 = 'RFC 3986';
18:
19: /**
20: * @var string Used to encode with urlencode
21: */
22: const FORM_URLENCODED = 'application/x-www-form-urlencoded';
23:
24: /**
25: * @var string Constant used to create blank query string values (e.g. ?foo)
26: */
27: const BLANK = "_guzzle_blank_";
28:
29: /**
30: * @var string The query string field separator (e.g. '&')
31: */
32: protected $fieldSeparator = '&';
33:
34: /**
35: * @var string The query string value separator (e.g. '=')
36: */
37: protected $valueSeparator = '=';
38:
39: /**
40: * @var bool URL encode fields and values?
41: */
42: protected $urlEncode = 'RFC 3986';
43:
44: /**
45: * @var QueryAggregatorInterface
46: */
47: protected $aggregator;
48:
49: /**
50: * @var array Cached PHP aggregator
51: */
52: protected static $defaultAggregator = null;
53:
54: /**
55: * Parse a query string into a QueryString object
56: *
57: * @param string $query Query string to parse
58: *
59: * @return self
60: */
61: public static function fromString($query)
62: {
63: $q = new static();
64:
65: if (0 !== strlen($query)) {
66: if ($query[0] == '?') {
67: $query = substr($query, 1);
68: }
69: foreach (explode('&', $query) as $kvp) {
70: $parts = explode('=', $kvp, 2);
71: $key = rawurldecode($parts[0]);
72:
73: $paramIsPhpStyleArray = substr($key, -2) == '[]';
74: if ($paramIsPhpStyleArray) {
75: $key = substr($key, 0, -2);
76: }
77:
78: if (array_key_exists(1, $parts)) {
79: $value = rawurldecode(str_replace('+', '%20', $parts[1]));
80: if ($paramIsPhpStyleArray && !$q->hasKey($key)) {
81: $value = array($value);
82: }
83: $q->add($key, $value);
84: } else {
85: $q->add($key, '');
86: }
87: }
88: }
89:
90: return $q;
91: }
92:
93: /**
94: * Convert the query string parameters to a query string string
95: *
96: * @return string
97: */
98: public function __toString()
99: {
100: if (empty($this->data)) {
101: return '';
102: }
103:
104: $queryString = '';
105: $firstValue = true;
106:
107: foreach ($this->prepareData($this->data) as $name => $value) {
108: $value = $value === null ? array('') : (array) $value;
109: foreach ($value as $v) {
110: if ($firstValue) {
111: $firstValue = false;
112: } else {
113: $queryString .= $this->fieldSeparator;
114: }
115: $queryString .= $name;
116: if ($v !== self::BLANK) {
117: $queryString .= $this->valueSeparator . $v;
118: }
119: }
120: }
121:
122: return $queryString;
123: }
124:
125: /**
126: * Get the query string field separator
127: *
128: * @return string
129: */
130: public function getFieldSeparator()
131: {
132: return $this->fieldSeparator;
133: }
134:
135: /**
136: * Get the query string value separator
137: *
138: * @return string
139: */
140: public function getValueSeparator()
141: {
142: return $this->valueSeparator;
143: }
144:
145: /**
146: * Returns the type of URL encoding used by the query string
147: *
148: * One of: false, "RFC 3986", or "application/x-www-form-urlencoded"
149: *
150: * @return bool|string
151: */
152: public function getUrlEncoding()
153: {
154: return $this->urlEncode;
155: }
156:
157: /**
158: * Returns true or false if using URL encoding
159: *
160: * @return bool
161: */
162: public function isUrlEncoding()
163: {
164: return $this->urlEncode !== false;
165: }
166:
167: /**
168: * Provide a function for combining multi-valued query string parameters into a single or multiple fields
169: *
170: * @param null|QueryAggregatorInterface $aggregator Pass in a QueryAggregatorInterface object to handle converting
171: * deeply nested query string variables into a flattened array.
172: * Pass null to use the default PHP style aggregator. For legacy
173: * reasons, this function accepts a callable that must accepts a
174: * $key, $value, and query object.
175: * @return self
176: * @see \Guzzle\Http\QueryString::aggregateUsingComma()
177: */
178: public function setAggregator(QueryAggregatorInterface $aggregator = null)
179: {
180: // Use the default aggregator if none was set
181: if (!$aggregator) {
182: if (!self::$defaultAggregator) {
183: self::$defaultAggregator = new PhpAggregator();
184: }
185: $aggregator = self::$defaultAggregator;
186: }
187:
188: $this->aggregator = $aggregator;
189:
190: return $this;
191: }
192:
193: /**
194: * Set whether or not field names and values should be rawurlencoded
195: *
196: * @param bool|string $encode Set to TRUE to use RFC 3986 encoding (rawurlencode), false to disable encoding, or
197: * form_urlencoding to use application/x-www-form-urlencoded encoding (urlencode)
198: * @return self
199: */
200: public function useUrlEncoding($encode)
201: {
202: $this->urlEncode = ($encode === true) ? self::RFC_3986 : $encode;
203:
204: return $this;
205: }
206:
207: /**
208: * Set the query string separator
209: *
210: * @param string $separator The query string separator that will separate fields
211: *
212: * @return self
213: */
214: public function setFieldSeparator($separator)
215: {
216: $this->fieldSeparator = $separator;
217:
218: return $this;
219: }
220:
221: /**
222: * Set the query string value separator
223: *
224: * @param string $separator The query string separator that will separate values from fields
225: *
226: * @return self
227: */
228: public function setValueSeparator($separator)
229: {
230: $this->valueSeparator = $separator;
231:
232: return $this;
233: }
234:
235: /**
236: * Returns an array of url encoded field names and values
237: *
238: * @return array
239: */
240: public function urlEncode()
241: {
242: return $this->prepareData($this->data);
243: }
244:
245: /**
246: * URL encodes a value based on the url encoding type of the query string object
247: *
248: * @param string $value Value to encode
249: *
250: * @return string
251: */
252: public function encodeValue($value)
253: {
254: if ($this->urlEncode == self::RFC_3986) {
255: return rawurlencode($value);
256: } elseif ($this->urlEncode == self::FORM_URLENCODED) {
257: return urlencode($value);
258: } else {
259: return (string) $value;
260: }
261: }
262:
263: /**
264: * Url encode parameter data and convert nested query strings into a flattened hash.
265: *
266: * @param array $data The data to encode
267: *
268: * @return array Returns an array of encoded values and keys
269: */
270: protected function prepareData(array $data)
271: {
272: // If no aggregator is present then set the default
273: if (!$this->aggregator) {
274: $this->setAggregator(null);
275: }
276:
277: $temp = array();
278: foreach ($data as $key => $value) {
279: if (is_array($value)) {
280: $temp = array_merge($temp, $this->aggregator->aggregate($key, $value, $this));
281: } else {
282: $temp[$this->encodeValue($key)] = $this->encodeValue($value);
283: }
284: }
285:
286: return $temp;
287: }
288: }
289: