Completed
Push — master ( 97ef46...8fa479 )
by smiley
01:36
created

message_helpers.php ➔ message_to_string()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 7
nop 1
dl 0
loc 23
rs 9.2408
c 0
b 0
f 0
1
<?php
2
/**
3
 * @filesource   message_helpers.php
4
 * @created      28.08.2018
5
 * @author       smiley <[email protected]>
6
 * @copyright    2018 smiley
7
 * @license      MIT
8
 */
9
10
namespace chillerlan\HTTP\Psr7;
11
12
use InvalidArgumentException;
13
use Psr\Http\Message\{MessageInterface, RequestInterface, ResponseInterface, UploadedFileInterface};
14
15
const PSR7_INCLUDES = true;
16
17
/**
18
 * Normalizes an array of header lines to format "Name: Value"
19
 *
20
 * @param array $headers
21
 *
22
 * @return array
23
 */
24
function normalize_request_headers(array $headers):array{
25
	$normalized_headers = [];
26
27
	foreach($headers as $key => $val){
28
29
		if(is_numeric($key)){
30
31
			if(is_string($val)){
32
				$header = explode(':', $val, 2);
33
34
				if(count($header) !== 2){
35
					continue;
36
				}
37
38
				$key = $header[0];
39
				$val = $header[1];
40
			}
41
			elseif(is_array($val)){
42
				$key = array_keys($val)[0];
43
				$val = array_values($val)[0];
44
			}
45
			else{
46
				continue;
47
			}
48
		}
49
50
		$key = strtolower(trim($key));
51
52
		$normalized_headers[$key] = trim($val);
53
	}
54
55
	return $normalized_headers;
56
}
57
58
/**
59
 * @param mixed $data
60
 *
61
 * @return mixed
62
 */
63
function raw_urlencode($data){
64
65
	if(is_array($data)){
66
		return array_map(__NAMESPACE__.'\\raw_urlencode', $data);
67
	}
68
69
	return rawurlencode($data);
70
}
71
72
/**
73
 * from https://github.com/abraham/twitteroauth/blob/master/src/Util.php
74
 *
75
 * @param array  $params
76
 * @param bool   $urlencode
77
 * @param string $delimiter
78
 * @param string $enclosure
79
 *
80
 * @return string
81
 */
82
function build_http_query(array $params, bool $urlencode = null, string $delimiter = null, string $enclosure = null):string{
83
84
	if(empty($params)){
85
		return '';
86
	}
87
88
	// urlencode both keys and values
89
	if($urlencode ?? true){
90
		$params = array_combine(
91
			raw_urlencode(array_keys($params)),
92
			raw_urlencode(array_values($params))
93
		);
94
	}
95
96
	// Parameters are sorted by name, using lexicographical byte value ordering.
97
	// Ref: Spec: 9.1.1 (1)
98
	uksort($params, 'strcmp');
99
100
	$pairs     = [];
101
	$enclosure = $enclosure ?? '';
102
103
	foreach($params as $parameter => $value){
104
105
		if(is_array($value)){
106
			// If two or more parameters share the same name, they are sorted by their value
107
			// Ref: Spec: 9.1.1 (1)
108
			// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
109
			sort($value, SORT_STRING);
110
111
			foreach($value as $duplicateValue){
112
				$pairs[] = $parameter.'='.$enclosure.$duplicateValue.$enclosure;
113
			}
114
115
		}
116
		else{
117
			$pairs[] = $parameter.'='.$enclosure.$value.$enclosure;
118
		}
119
120
	}
121
122
	// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
123
	// Each name-value pair is separated by an '&' character (ASCII code 38)
124
	return implode($delimiter ?? '&', $pairs);
125
}
126
127
const BOOLEANS_AS_BOOL = 0;
128
const BOOLEANS_AS_INT = 1;
129
const BOOLEANS_AS_STRING = 2;
130
const BOOLEANS_AS_INT_STRING = 3;
131
/**
132
 * @param iterable  $params
133
 * @param int|null  $bool_cast converts booleans to a type determined like following:
134
 *                             BOOLEANS_AS_BOOL      : unchanged boolean value (default)
135
 *                             BOOLEANS_AS_INT       : integer values 0 or 1
136
 *                             BOOLEANS_AS_STRING    : "true"/"false" strings
137
 *                             BOOLEANS_AS_INT_STRING: "0"/"1"
138
 *
139
 * @param bool|null $remove_empty remove empty and NULL values
140
 *
141
 * @return array
142
 */
143
function clean_query_params(iterable $params, int $bool_cast = null, bool $remove_empty = null):array{
144
	$p = [];
145
	$bool_cast = $bool_cast ?? BOOLEANS_AS_BOOL;
146
	$remove_empty = $remove_empty ?? true;
147
148
	foreach($params as $key => $value){
149
150
		if(is_bool($value)){
151
152
			if($bool_cast === BOOLEANS_AS_BOOL){
153
				$p[$key] = $value;
154
			}
155
			elseif($bool_cast === BOOLEANS_AS_INT){
156
				$p[$key] = (int)$value;
157
			}
158
			elseif($bool_cast === BOOLEANS_AS_STRING){
159
				$p[$key] = $value ? 'true' : 'false';
160
			}
161
			elseif($bool_cast === BOOLEANS_AS_INT_STRING){
162
				$p[$key] = (string)(int)$value;
163
			}
164
165
		}
166
		elseif($remove_empty === true && ($value === null || (!is_numeric($value) && empty($value)))){
167
			continue;
168
		}
169
		else{
170
			$p[$key] = $value;
171
		}
172
	}
173
174
	return $p;
175
}
176
177
/**
178
 * merges additional query parameters into an existing query string
179
 *
180
 * @param string $uri
181
 * @param array  $query
182
 *
183
 * @return string
184
 */
185
function merge_query(string $uri, array $query):string{
186
	parse_str(parse_url($uri, PHP_URL_QUERY), $parsedquery);
187
188
	$requestURI = explode('?', $uri)[0];
189
	$params     = array_merge($parsedquery, $query);
190
191
	if(!empty($params)){
192
		$requestURI .= '?'.build_http_query($params);
193
	}
194
195
	return $requestURI;
196
}
197
198
/**
199
 * Return an UploadedFile instance array.
200
 *
201
 * @param array $files A array which respect $_FILES structure
202
 *
203
 * @throws \InvalidArgumentException for unrecognized values
204
 * @return array
205
 */
206
function normalize_files(array $files):array{
207
	$normalized = [];
208
209
	foreach($files as $key => $value){
210
211
		if($value instanceof UploadedFileInterface){
212
			$normalized[$key] = $value;
213
		}
214
		elseif(is_array($value) && isset($value['tmp_name'])){
215
			$normalized[$key] = create_uploaded_file_from_spec($value);
216
		}
217
		elseif(is_array($value)){
218
			$normalized[$key] = normalize_files($value);
219
			continue;
220
		}
221
		else{
222
			throw new InvalidArgumentException('Invalid value in files specification');
223
		}
224
225
	}
226
227
	return $normalized;
228
}
229
230
/**
231
 * Create and return an UploadedFile instance from a $_FILES specification.
232
 *
233
 * If the specification represents an array of values, this method will
234
 * delegate to normalizeNestedFileSpec() and return that return value.
235
 *
236
 * @param array $value $_FILES struct
237
 *
238
 * @return array|\Psr\Http\Message\UploadedFileInterface
239
 */
240
function create_uploaded_file_from_spec(array $value){
241
242
	if(is_array($value['tmp_name'])){
243
		return normalize_nested_file_spec($value);
244
	}
245
246
	return new UploadedFile($value['tmp_name'], (int)$value['size'], (int)$value['error'], $value['name'], $value['type']);
247
}
248
249
/**
250
 * Normalize an array of file specifications.
251
 *
252
 * Loops through all nested files and returns a normalized array of
253
 * UploadedFileInterface instances.
254
 *
255
 * @param array $files
256
 *
257
 * @return \Psr\Http\Message\UploadedFileInterface[]
258
 */
259
function normalize_nested_file_spec(array $files = []):array{
260
	$normalizedFiles = [];
261
262
	foreach(array_keys($files['tmp_name']) as $key){
263
		$spec = [
264
			'tmp_name' => $files['tmp_name'][$key],
265
			'size'     => $files['size'][$key],
266
			'error'    => $files['error'][$key],
267
			'name'     => $files['name'][$key],
268
			'type'     => $files['type'][$key],
269
		];
270
271
		$normalizedFiles[$key] = create_uploaded_file_from_spec($spec);
272
	}
273
274
	return $normalizedFiles;
275
}
276
277
/**
278
 * @todo
279
 *
280
 * @param \Psr\Http\Message\ResponseInterface $response
281
 * @param bool|null                           $assoc
282
 *
283
 * @return mixed
284
 */
285
function get_json(ResponseInterface $response, bool $assoc = null){
286
	return json_decode($response->getBody()->getContents(), $assoc);
287
}
288
289
/**
290
 * @todo
291
 *
292
 * @param \Psr\Http\Message\ResponseInterface $response
293
 *
294
 * @return \SimpleXMLElement
295
 */
296
function get_xml(ResponseInterface $response){
297
	return simplexml_load_string($response->getBody()->getContents());
298
}
299
300
/**
301
 * Returns the string representation of an HTTP message. (from Guzzle)
302
 *
303
 * @param MessageInterface $message Message to convert to a string.
304
 *
305
 * @return string
306
 */
307
function message_to_string(MessageInterface $message){
308
309
	if($message instanceof RequestInterface){
310
		$msg = trim($message->getMethod().' '.$message->getRequestTarget()).' HTTP/'.$message->getProtocolVersion();
311
312
		if(!$message->hasHeader('host')){
313
			$msg .= "\r\nHost: ".$message->getUri()->getHost();
314
		}
315
316
	}
317
	elseif($message instanceof ResponseInterface){
318
		$msg = 'HTTP/'.$message->getProtocolVersion().' '.$message->getStatusCode().' '.$message->getReasonPhrase();
319
	}
320
	else{
321
		throw new \InvalidArgumentException('Unknown message type');
322
	}
323
324
	foreach($message->getHeaders() as $name => $values){
325
		$msg .= "\r\n{$name}: ".implode(', ', $values);
326
	}
327
328
	return "{$msg}\r\n\r\n".$message->getBody();
329
}
330