Passed
Push — main ( 404000...b70a85 )
by smiley
01:56
created

Query::build()   B

Complexity

Conditions 9
Paths 13

Size

Total Lines 59
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 28
c 1
b 0
f 0
nc 13
nop 4
dl 0
loc 59
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Class Query
4
 *
5
 * @created      27.03.2021
6
 * @author       smiley <[email protected]>
7
 * @copyright    2021 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\HTTP\Psr7;
12
13
use function array_merge, explode, implode, is_array, is_bool, is_string, parse_url, rawurldecode, sort, str_replace, uksort;
14
use const PHP_QUERY_RFC1738, PHP_QUERY_RFC3986, PHP_URL_QUERY, SORT_STRING;
15
16
/**
17
 *
18
 */
19
final class Query{
20
21
	public const BOOLEANS_AS_BOOL       = 0;
22
	public const BOOLEANS_AS_INT        = 1;
23
	public const BOOLEANS_AS_STRING     = 2;
24
	public const BOOLEANS_AS_INT_STRING = 3;
25
26
	public const NO_ENCODING = -1;
27
28
	/**
29
	 * @param iterable  $params
30
	 * @param int|null  $bool_cast    converts booleans to a type determined like following:
31
	 *                                BOOLEANS_AS_BOOL      : unchanged boolean value (default)
32
	 *                                BOOLEANS_AS_INT       : integer values 0 or 1
33
	 *                                BOOLEANS_AS_STRING    : "true"/"false" strings
34
	 *                                BOOLEANS_AS_INT_STRING: "0"/"1"
35
	 *
36
	 * @param bool|null $remove_empty remove empty and NULL values (default: true)
37
	 *
38
	 * @return array
39
	 */
40
	public static function cleanParams(iterable $params, int $bool_cast = null, bool $remove_empty = null):array{
41
		$bool_cast    ??= self::BOOLEANS_AS_BOOL;
42
		$remove_empty ??= true;
43
44
		$cleaned = [];
45
46
		foreach($params as $key => $value){
47
48
			if(is_iterable($value)){
49
				// recursion
50
				$cleaned[$key] = call_user_func_array(__METHOD__, [$value, $bool_cast, $remove_empty]);
51
			}
52
			elseif(is_bool($value)){
53
54
				if($bool_cast === self::BOOLEANS_AS_BOOL){
55
					$cleaned[$key] = $value;
56
				}
57
				elseif($bool_cast === self::BOOLEANS_AS_INT){
58
					$cleaned[$key] = (int)$value;
59
				}
60
				elseif($bool_cast === self::BOOLEANS_AS_STRING){
61
					$cleaned[$key] = $value ? 'true' : 'false';
62
				}
63
				elseif($bool_cast === self::BOOLEANS_AS_INT_STRING){
64
					$cleaned[$key] = (string)(int)$value;
65
				}
66
67
			}
68
			elseif(is_string($value)){
69
				$value = trim($value);
70
71
				if($remove_empty && empty($value)){
72
					continue;
73
				}
74
75
				$cleaned[$key] = $value;
76
			}
77
			else{
78
79
				if($remove_empty && ($value === null || (!is_numeric($value) && empty($value)))){
80
					continue;
81
				}
82
83
				$cleaned[$key] = $value;
84
			}
85
		}
86
87
		return $cleaned;
88
	}
89
90
	/**
91
	 * Build a query string from an array of key value pairs.
92
	 *
93
	 * Valid values for $encoding are PHP_QUERY_RFC3986 (default) and PHP_QUERY_RFC1738,
94
	 * any other integer value will be interpreted as "no encoding".
95
	 *
96
	 * @link https://github.com/abraham/twitteroauth/blob/57108b31f208d0066ab90a23257cdd7bb974c67d/src/Util.php#L84-L122
97
	 * @link https://github.com/guzzle/psr7/blob/c0dcda9f54d145bd4d062a6d15f54931a67732f9/src/Query.php#L59-L113
98
	 */
99
	public static function build(array $params, int $encoding = null, string $delimiter = null, string $enclosure = null):string{
100
101
		if(empty($params)){
102
			return '';
103
		}
104
105
		$encoding  ??= PHP_QUERY_RFC3986;
106
		$enclosure ??= '';
107
		$delimiter ??= '&';
108
109
		if($encoding === PHP_QUERY_RFC3986){
110
			$encode = 'rawurlencode';
111
		}
112
		elseif($encoding === PHP_QUERY_RFC1738){
113
			$encode = 'urlencode';
114
		}
115
		else{
116
			$encode = fn(string $str):string => $str;
117
		}
118
119
		$pair = function(string $key, $value) use ($encode, $enclosure):string{
120
121
			if($value === null){
122
				return $key;
123
			}
124
125
			if(is_bool($value)){
126
				$value = (int)$value;
127
			}
128
129
			// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
130
			return $key.'='.$enclosure.$encode((string)$value).$enclosure;
131
		};
132
133
		// Parameters are sorted by name, using lexicographical byte value ordering.
134
		uksort($params, 'strcmp');
135
136
		$pairs = [];
137
138
		foreach($params as $parameter => $value){
139
			$parameter = $encode((string)$parameter);
140
141
			if(is_array($value)){
142
				// If two or more parameters share the same name, they are sorted by their value
143
				sort($value, SORT_STRING);
144
145
				foreach($value as $duplicateValue){
146
					$pairs[] = $pair($parameter, $duplicateValue);
147
				}
148
149
			}
150
			else{
151
				$pairs[] = $pair($parameter, $value);
152
			}
153
154
		}
155
156
		// Each name-value pair is separated by an '&' character (ASCII code 38)
157
		return implode($delimiter, $pairs);
158
	}
159
160
	/**
161
	 * merges additional query parameters into an existing query string
162
	 *
163
	 * @param string $uri
164
	 * @param array  $query
165
	 *
166
	 * @return string
167
	 */
168
	public static function merge(string $uri, array $query):string{
169
		$parsedquery = self::parse(parse_url($uri, PHP_URL_QUERY) ?: '');
170
		$requestURI  = explode('?', $uri)[0];
171
		$params      = array_merge($parsedquery, $query);
172
173
		if(!empty($params)){
174
			$requestURI .= '?'.Query::build($params);
175
		}
176
177
		return $requestURI;
178
	}
179
180
	/**
181
	 * Parse a query string into an associative array.
182
	 *
183
	 * @link https://github.com/guzzle/psr7/blob/c0dcda9f54d145bd4d062a6d15f54931a67732f9/src/Query.php#L9-L57
184
	 */
185
	public static function parse(string $querystring, int $urlEncoding = null):array{
186
187
		if($querystring === ''){
188
			return [];
189
		}
190
191
		if($urlEncoding === self::NO_ENCODING){
192
			$decode = fn(string $str):string => $str;
193
		}
194
		elseif($urlEncoding === PHP_QUERY_RFC3986){
195
			$decode = 'rawurldecode';
196
		}
197
		elseif($urlEncoding === PHP_QUERY_RFC1738){
198
			$decode = 'urldecode';
199
		}
200
		else{
201
			$decode = fn(string $value):string => rawurldecode(str_replace('+', ' ', $value));
202
		}
203
204
		$result = [];
205
206
		foreach(explode('&', $querystring) as $pair){
207
			$parts = explode('=', $pair, 2);
208
			$key   = $decode($parts[0]);
209
			$value = isset($parts[1]) ? $decode($parts[1]) : null;
210
211
			if(!isset($result[$key])){
212
				$result[$key] = $value;
213
			}
214
			else{
215
216
				if(!is_array($result[$key])){
217
					$result[$key] = [$result[$key]];
218
				}
219
220
				$result[$key][] = $value;
221
			}
222
		}
223
224
		return $result;
225
	}
226
227
}
228