1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace Igni\Network\Http; |
4
|
|
|
|
5
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
6
|
|
|
use Psr\Http\Message\StreamInterface; |
7
|
|
|
use Psr\Http\Message\UploadedFileInterface; |
8
|
|
|
use Swoole\Http\Request as SwooleHttRequest; |
9
|
|
|
use Throwable; |
10
|
|
|
|
11
|
|
|
use function Zend\Diactoros\marshalHeadersFromSapi; |
12
|
|
|
use function Zend\Diactoros\normalizeUploadedFiles; |
13
|
|
|
|
14
|
|
|
class ServerRequest extends Request implements ServerRequestInterface |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* @var array |
18
|
|
|
*/ |
19
|
|
|
private $attributes = []; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var array |
23
|
|
|
*/ |
24
|
|
|
private $cookieParams = []; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var null|array|object |
28
|
|
|
*/ |
29
|
|
|
private $parsedBody; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
private $queryParams = []; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var array |
38
|
|
|
*/ |
39
|
|
|
private $serverParams; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var array |
43
|
|
|
*/ |
44
|
|
|
private $uploadedFiles; |
45
|
|
|
|
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Server request constructor. |
49
|
|
|
* |
50
|
|
|
* @param array $serverParams Server parameters, typically from $_SERVER |
51
|
|
|
* @param array $uploadedFiles Upload file information, a tree of UploadedFiles |
52
|
|
|
* @param null|string $uri URI for the request, if any. |
53
|
|
|
* @param null|string $method HTTP method for the request, if any. |
54
|
|
|
* @param string|resource|StreamInterface $body Messages body, if any. |
55
|
|
|
* @param array $headers Headers for the message, if any. |
56
|
|
|
* @throws \InvalidArgumentException for any invalid value. |
57
|
|
|
*/ |
58
|
10 |
|
public function __construct( |
59
|
|
|
string $uri = null, |
60
|
|
|
string $method = self::METHOD_GET, |
61
|
|
|
$body = 'php://input', |
62
|
|
|
array $headers = [], |
63
|
|
|
array $uploadedFiles = [], |
64
|
|
|
array $serverParams = [] |
65
|
|
|
) { |
66
|
10 |
|
parent::__construct($uri, $method, $body, $headers); |
67
|
10 |
|
$this->validateUploadedFiles($uploadedFiles); |
68
|
10 |
|
$this->serverParams = $serverParams; |
69
|
10 |
|
$this->uploadedFiles = $uploadedFiles; |
70
|
10 |
|
parse_str($this->getUri()->getQuery(), $this->queryParams); |
71
|
10 |
|
$this->parseBody(); |
72
|
10 |
|
} |
73
|
|
|
|
74
|
10 |
|
private function parseBody(): void |
75
|
|
|
{ |
76
|
10 |
|
$contentType = $this->getHeader('Content-Type')[0] ?? ''; |
77
|
|
|
|
78
|
10 |
|
$body = (string) $this->getBody(); |
79
|
|
|
|
80
|
10 |
|
switch (strtolower($contentType)) { |
81
|
10 |
|
case 'application/json': |
|
|
|
|
82
|
1 |
|
$this->parsedBody = json_decode($body, true); |
83
|
1 |
|
return; |
84
|
9 |
|
case 'application/x-www-form-urlencoded': |
|
|
|
|
85
|
1 |
|
parse_str($body, $this->parsedBody); |
86
|
1 |
|
return; |
87
|
8 |
|
case 'application/xml': |
88
|
|
|
$this->parsedBody = simplexml_load_string($body); |
89
|
|
|
return; |
90
|
8 |
|
case 'text/csv': |
91
|
|
|
$this->parsedBody = str_getcsv($body); |
92
|
|
|
return; |
93
|
|
|
} |
94
|
8 |
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* {@inheritdoc} |
98
|
|
|
*/ |
99
|
2 |
|
public function getServerParams(): array |
100
|
|
|
{ |
101
|
2 |
|
return $this->serverParams; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* {@inheritdoc} |
106
|
|
|
*/ |
107
|
2 |
|
public function getUploadedFiles(): array |
108
|
|
|
{ |
109
|
2 |
|
return $this->uploadedFiles; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* {@inheritdoc} |
114
|
|
|
*/ |
115
|
1 |
|
public function withUploadedFiles(array $uploadedFiles) |
116
|
|
|
{ |
117
|
1 |
|
$this->validateUploadedFiles($uploadedFiles); |
118
|
1 |
|
$new = clone $this; |
119
|
1 |
|
$new->uploadedFiles = $uploadedFiles; |
120
|
|
|
|
121
|
1 |
|
return $new; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* {@inheritdoc} |
126
|
|
|
*/ |
127
|
2 |
|
public function getCookieParams(): array |
128
|
|
|
{ |
129
|
2 |
|
return $this->cookieParams; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* {@inheritdoc} |
134
|
|
|
*/ |
135
|
1 |
|
public function withCookieParams(array $cookies) |
136
|
|
|
{ |
137
|
1 |
|
$new = clone $this; |
138
|
1 |
|
$new->cookieParams = $cookies; |
139
|
1 |
|
return $new; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* {@inheritdoc} |
144
|
|
|
*/ |
145
|
4 |
|
public function getQueryParams(): array |
146
|
|
|
{ |
147
|
4 |
|
return $this->queryParams; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* {@inheritdoc} |
152
|
|
|
*/ |
153
|
1 |
|
public function withQueryParams(array $query) |
154
|
|
|
{ |
155
|
1 |
|
$new = clone $this; |
156
|
1 |
|
$new->queryParams = $query; |
157
|
|
|
|
158
|
1 |
|
return $new; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* {@inheritdoc} |
163
|
|
|
*/ |
164
|
4 |
|
public function getParsedBody() |
165
|
|
|
{ |
166
|
4 |
|
return $this->parsedBody; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* {@inheritdoc} |
171
|
|
|
*/ |
172
|
1 |
|
public function withParsedBody($data) |
173
|
|
|
{ |
174
|
1 |
|
$new = clone $this; |
175
|
1 |
|
$new->parsedBody = $data; |
176
|
1 |
|
return $new; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* {@inheritdoc} |
181
|
|
|
*/ |
182
|
2 |
|
public function getAttributes(): array |
183
|
|
|
{ |
184
|
2 |
|
return $this->attributes; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* {@inheritdoc} |
189
|
|
|
*/ |
190
|
1 |
|
public function getAttribute($attribute, $default = null) |
191
|
|
|
{ |
192
|
1 |
|
if (! isset($this->attributes[$attribute])) { |
193
|
1 |
|
return $default; |
194
|
|
|
} |
195
|
|
|
|
196
|
1 |
|
return $this->attributes[$attribute]; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* {@inheritdoc} |
201
|
|
|
*/ |
202
|
1 |
|
public function withAttribute($attribute, $value) |
203
|
|
|
{ |
204
|
1 |
|
$new = clone $this; |
205
|
1 |
|
$new->attributes[$attribute] = $value; |
206
|
1 |
|
return $new; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* {@inheritdoc} |
211
|
|
|
*/ |
212
|
1 |
|
public function withoutAttribute($attribute) |
213
|
|
|
{ |
214
|
1 |
|
if (!isset($this->attributes[$attribute])) { |
215
|
1 |
|
return clone $this; |
216
|
|
|
} |
217
|
|
|
|
218
|
1 |
|
$new = clone $this; |
219
|
1 |
|
unset($new->attributes[$attribute]); |
220
|
1 |
|
return $new; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Sets request attributes |
225
|
|
|
* |
226
|
|
|
* This method returns a new instance. |
227
|
|
|
* |
228
|
|
|
* @param array $attributes |
229
|
|
|
* @return self |
230
|
|
|
*/ |
231
|
1 |
|
public function withAttributes(array $attributes): ServerRequest |
232
|
|
|
{ |
233
|
1 |
|
$new = clone $this; |
234
|
1 |
|
$new->attributes = $attributes; |
235
|
1 |
|
return $new; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Recursively validate the structure in an uploaded files array. |
240
|
|
|
* |
241
|
|
|
* @param array $uploadedFiles |
242
|
|
|
* @throws \InvalidArgumentException if any leaf is not an UploadedFileInterface instance. |
243
|
|
|
*/ |
244
|
10 |
|
private function validateUploadedFiles(array $uploadedFiles): void |
245
|
|
|
{ |
246
|
10 |
|
foreach ($uploadedFiles as $file) { |
247
|
1 |
|
if (is_array($file)) { |
248
|
|
|
$this->validateUploadedFiles($file); |
249
|
|
|
continue; |
250
|
|
|
} |
251
|
|
|
|
252
|
1 |
|
if (! $file instanceof UploadedFileInterface) { |
|
|
|
|
253
|
|
|
throw new \InvalidArgumentException('Invalid leaf in uploaded files structure'); |
254
|
|
|
} |
255
|
|
|
} |
256
|
10 |
|
} |
257
|
|
|
|
258
|
2 |
|
public static function fromGlobals(): ServerRequest |
259
|
|
|
{ |
260
|
2 |
|
$instance = new self( |
261
|
2 |
|
$_SERVER['REQUEST_URI'] ?? '', |
262
|
2 |
|
$_SERVER['REQUEST_METHOD'] ?? 'GET', |
263
|
2 |
|
'php://input', |
264
|
2 |
|
marshalHeadersFromSapi($_SERVER), |
265
|
2 |
|
normalizeUploadedFiles($_FILES), |
266
|
2 |
|
$_SERVER |
267
|
|
|
); |
268
|
|
|
|
269
|
2 |
|
return $instance; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* @param SwooleHttRequest $request |
274
|
|
|
* @return ServerRequest |
275
|
|
|
*/ |
276
|
4 |
|
public static function fromSwoole(SwooleHttRequest $request): ServerRequest |
277
|
|
|
{ |
278
|
4 |
|
$serverParams = $request->server ?? []; |
279
|
4 |
|
if (isset($request->server['query_string'])) { |
280
|
1 |
|
$uri = $request->server['request_uri'] . '?' . $request->server['query_string']; |
281
|
|
|
} else { |
282
|
3 |
|
$uri = $request->server['request_uri']; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
// Normalize server params |
286
|
4 |
|
$serverParams = array_change_key_case($serverParams, CASE_UPPER); |
287
|
|
|
|
288
|
|
|
// Normalize headers |
289
|
4 |
|
$headers = []; |
290
|
4 |
|
if ($request->header) { |
291
|
2 |
|
foreach ($request->header as $name => $value) { |
292
|
2 |
|
if (!isset($headers[$name])) { |
293
|
2 |
|
$headers[$name] = []; |
294
|
|
|
} |
295
|
2 |
|
array_push($headers[$name], $value); |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
try { |
300
|
4 |
|
$body = $request->rawContent(); |
301
|
4 |
|
} catch (Throwable $throwable) { |
302
|
4 |
|
$body = ''; |
303
|
|
|
} |
304
|
|
|
|
305
|
4 |
|
if (!$body) { |
306
|
4 |
|
$body = ''; |
307
|
|
|
} |
308
|
|
|
|
309
|
4 |
|
return new ServerRequest( |
310
|
4 |
|
$uri, |
311
|
4 |
|
$request->server['request_method'] ?? 'GET', |
312
|
4 |
|
$body, |
313
|
4 |
|
$headers, |
314
|
4 |
|
normalizeUploadedFiles($request->files ?? []) ?? [], |
315
|
4 |
|
$serverParams |
316
|
|
|
); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.