Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
90 / 90 |
|
100.00% |
23 / 23 |
CRAP | |
100.00% |
1 / 1 |
ServerRequest | |
100.00% |
90 / 90 |
|
100.00% |
23 / 23 |
40 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
normalizeFiles | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
createUploadedFileFromSpec | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
normalizeNestedFileSpec | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
fromGlobals | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
getUriFromGlobals | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
getServerParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUploadedFiles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
withUploadedFiles | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getCookieParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
withCookieParams | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getQueryParams | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
withQueryParams | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getParsedBody | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
withParsedBody | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getAttributes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAttribute | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
withAttribute | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
withoutAttribute | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getCookie | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasCookie | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelativePath | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getBase | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace Tal; |
4 | |
5 | use GuzzleHttp\Psr7\CachingStream; |
6 | use GuzzleHttp\Psr7\LazyOpenStream; |
7 | use GuzzleHttp\Psr7\UploadedFile; |
8 | use GuzzleHttp\Psr7\Uri; |
9 | use InvalidArgumentException; |
10 | use Psr\Http\Message\RequestInterface; |
11 | use Psr\Http\Message\ServerRequestInterface as PsrServerRequestInterface; |
12 | use Psr\Http\Message\UriInterface; |
13 | use Psr\Http\Message\StreamInterface; |
14 | use Psr\Http\Message\UploadedFileInterface; |
15 | use Tal\Psr7Extended\ServerRequestInterface; |
16 | |
17 | /** |
18 | * Server-side HTTP request |
19 | * |
20 | * Extends the Request definition to add methods for accessing incoming data, |
21 | * specifically server parameters, cookies, matched path parameters, query |
22 | * string arguments, body parameters, and upload file information. |
23 | * |
24 | * "Attributes" are discovered via decomposing the request (and usually |
25 | * specifically the URI path), and typically will be injected by the application. |
26 | * |
27 | * Requests are considered immutable; all methods that might change state are |
28 | * implemented such that they retain the internal state of the current |
29 | * message and return a new instance that contains the changed state. |
30 | */ |
31 | class ServerRequest extends Request implements ServerRequestInterface |
32 | { |
33 | /** |
34 | * @var array |
35 | */ |
36 | protected $attributes = []; |
37 | |
38 | /** |
39 | * @var array |
40 | */ |
41 | protected $cookieParams = []; |
42 | |
43 | /** |
44 | * @var null|array|object |
45 | */ |
46 | protected $parsedBody; |
47 | |
48 | /** |
49 | * @var array |
50 | */ |
51 | protected $queryParams = []; |
52 | |
53 | /** |
54 | * @var array |
55 | */ |
56 | protected $serverParams; |
57 | |
58 | /** |
59 | * @var array |
60 | */ |
61 | protected $uploadedFiles = []; |
62 | |
63 | /** |
64 | * @param string $method HTTP method |
65 | * @param string|UriInterface $uri URI |
66 | * @param array $headers Request headers |
67 | * @param string|null|resource|StreamInterface $body Request body |
68 | * @param string $version Protocol version |
69 | * @param array $serverParams Typically the $_SERVER superglobal |
70 | */ |
71 | public function __construct( |
72 | $method, |
73 | $uri, |
74 | array $headers = [], |
75 | $body = null, |
76 | $version = '1.1', |
77 | array $serverParams = [] |
78 | ) { |
79 | $this->serverParams = $serverParams; |
80 | |
81 | parent::__construct($method, $uri, $headers, $body, $version); |
82 | } |
83 | |
84 | /** |
85 | * Return an UploadedFile instance array. |
86 | * |
87 | * @param array $files A array which respect $_FILES structure |
88 | * @throws InvalidArgumentException for unrecognized values |
89 | * @return array |
90 | */ |
91 | public static function normalizeFiles(array $files) |
92 | { |
93 | $normalized = []; |
94 | |
95 | foreach ($files as $key => $value) { |
96 | if ($value instanceof UploadedFileInterface) { |
97 | $normalized[$key] = $value; |
98 | } elseif (is_array($value) && isset($value['tmp_name'])) { |
99 | $normalized[$key] = self::createUploadedFileFromSpec($value); |
100 | } elseif (is_array($value)) { |
101 | $normalized[$key] = self::normalizeFiles($value); |
102 | continue; |
103 | } else { |
104 | throw new InvalidArgumentException('Invalid value in files specification'); |
105 | } |
106 | } |
107 | |
108 | return $normalized; |
109 | } |
110 | |
111 | /** |
112 | * Create and return an UploadedFile instance from a $_FILES specification. |
113 | * |
114 | * If the specification represents an array of values, this method will |
115 | * delegate to normalizeNestedFileSpec() and return that return value. |
116 | * |
117 | * @param array $value $_FILES struct |
118 | * @return array|UploadedFileInterface |
119 | */ |
120 | protected static function createUploadedFileFromSpec(array $value) |
121 | { |
122 | if (is_array($value['tmp_name'])) { |
123 | return self::normalizeNestedFileSpec($value); |
124 | } |
125 | |
126 | return new UploadedFile( |
127 | $value['tmp_name'], |
128 | (int) $value['size'], |
129 | (int) $value['error'], |
130 | $value['name'], |
131 | $value['type'] |
132 | ); |
133 | } |
134 | |
135 | /** |
136 | * Normalize an array of file specifications. |
137 | * |
138 | * Loops through all nested files and returns a normalized array of |
139 | * UploadedFileInterface instances. |
140 | * |
141 | * @param array $files |
142 | * @return UploadedFileInterface[] |
143 | */ |
144 | protected static function normalizeNestedFileSpec(array $files = []) |
145 | { |
146 | $normalizedFiles = []; |
147 | |
148 | foreach (array_keys($files['tmp_name']) as $key) { |
149 | $spec = [ |
150 | 'tmp_name' => $files['tmp_name'][$key], |
151 | 'size' => $files['size'][$key], |
152 | 'error' => $files['error'][$key], |
153 | 'name' => $files['name'][$key], |
154 | 'type' => $files['type'][$key], |
155 | ]; |
156 | $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); |
157 | } |
158 | |
159 | return $normalizedFiles; |
160 | } |
161 | |
162 | /** |
163 | * Return a ServerRequestInterface populated with superglobals: |
164 | * $_GET |
165 | * $_POST |
166 | * $_COOKIE |
167 | * $_FILES |
168 | * $_SERVER |
169 | * |
170 | * @return ServerRequestInterface |
171 | */ |
172 | public static function fromGlobals() |
173 | { |
174 | $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; |
175 | $headers = function_exists('getallheaders') ? getallheaders() : []; |
176 | $uri = self::getUriFromGlobals(); |
177 | $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); |
178 | $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; |
179 | |
180 | $serverRequest = new static($method, $uri, $headers, $body, $protocol, $_SERVER); |
181 | |
182 | return $serverRequest |
183 | ->withCookieParams($_COOKIE) |
184 | ->withQueryParams($_GET) |
185 | ->withParsedBody($_POST) |
186 | ->withUploadedFiles(self::normalizeFiles($_FILES)); |
187 | } |
188 | |
189 | /** |
190 | * Get a Uri populated with values from $_SERVER. |
191 | * |
192 | * @return UriInterface |
193 | */ |
194 | public static function getUriFromGlobals() |
195 | { |
196 | $uri = new Uri(''); |
197 | |
198 | $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') |
199 | ->withHost(Server::getHost() ?? '') |
200 | ->withPort(Server::getPort()); |
201 | |
202 | $path = Server::getPath(); |
203 | if ($path !== null) { |
204 | $uri = $uri->withPath($path); |
205 | } |
206 | |
207 | $query = Server::getQuery(); |
208 | if ($query !== null) { |
209 | $uri = $uri->withQuery($query); |
210 | } |
211 | |
212 | return $uri; |
213 | } |
214 | |
215 | |
216 | /** |
217 | * {@inheritdoc} |
218 | */ |
219 | public function getServerParams(): array |
220 | { |
221 | return $this->serverParams; |
222 | } |
223 | |
224 | /** |
225 | * {@inheritdoc} |
226 | */ |
227 | public function getUploadedFiles(): array |
228 | { |
229 | return $this->uploadedFiles; |
230 | } |
231 | |
232 | /** |
233 | * {@inheritdoc} |
234 | */ |
235 | public function withUploadedFiles(array $uploadedFiles): PsrServerRequestInterface |
236 | { |
237 | $new = clone $this; |
238 | $new->uploadedFiles = $uploadedFiles; |
239 | |
240 | return $new; |
241 | } |
242 | |
243 | /** |
244 | * {@inheritdoc} |
245 | */ |
246 | public function getCookieParams(): array |
247 | { |
248 | return $this->cookieParams; |
249 | } |
250 | |
251 | /** |
252 | * {@inheritdoc} |
253 | */ |
254 | public function withCookieParams(array $cookies): PsrServerRequestInterface |
255 | { |
256 | $new = clone $this; |
257 | $new->cookieParams = $cookies; |
258 | |
259 | return $new; |
260 | } |
261 | |
262 | /** |
263 | * {@inheritdoc} |
264 | */ |
265 | public function getQueryParams(): array |
266 | { |
267 | return $this->queryParams; |
268 | } |
269 | |
270 | /** |
271 | * {@inheritdoc} |
272 | */ |
273 | public function withQueryParams(array $query): PsrServerRequestInterface |
274 | { |
275 | $new = clone $this; |
276 | $new->queryParams = $query; |
277 | |
278 | return $new; |
279 | } |
280 | |
281 | /** |
282 | * {@inheritdoc} |
283 | */ |
284 | public function getParsedBody() |
285 | { |
286 | return $this->parsedBody; |
287 | } |
288 | |
289 | /** |
290 | * {@inheritdoc} |
291 | */ |
292 | public function withParsedBody($data): PsrServerRequestInterface |
293 | { |
294 | $new = clone $this; |
295 | $new->parsedBody = $data; |
296 | |
297 | return $new; |
298 | } |
299 | |
300 | /** |
301 | * {@inheritdoc} |
302 | */ |
303 | public function getAttributes(): array |
304 | { |
305 | return $this->attributes; |
306 | } |
307 | |
308 | /** |
309 | * {@inheritdoc} |
310 | */ |
311 | public function getAttribute(string $attribute, $default = null) |
312 | { |
313 | if (false === array_key_exists($attribute, $this->attributes)) { |
314 | return $default; |
315 | } |
316 | |
317 | return $this->attributes[$attribute]; |
318 | } |
319 | |
320 | /** |
321 | * {@inheritdoc} |
322 | */ |
323 | public function withAttribute(string $attribute, $value): PsrServerRequestInterface |
324 | { |
325 | $new = clone $this; |
326 | $new->attributes[$attribute] = $value; |
327 | |
328 | return $new; |
329 | } |
330 | |
331 | /** |
332 | * {@inheritdoc} |
333 | */ |
334 | public function withoutAttribute(string $attribute): PsrServerRequestInterface |
335 | { |
336 | if (false === array_key_exists($attribute, $this->attributes)) { |
337 | return $this; |
338 | } |
339 | |
340 | $new = clone $this; |
341 | unset($new->attributes[$attribute]); |
342 | |
343 | return $new; |
344 | } |
345 | |
346 | /** |
347 | * Get cookie by name |
348 | * |
349 | * @param string $name |
350 | * @param mixed $default |
351 | * @return mixed |
352 | */ |
353 | public function getCookie(string $name, $default = null) |
354 | { |
355 | return $this->getCookieParams()[$name] ?? $default; |
356 | } |
357 | |
358 | /** |
359 | * Check if cookie is set |
360 | * |
361 | * @param string $name |
362 | * @return bool |
363 | */ |
364 | public function hasCookie(string $name): bool |
365 | { |
366 | return isset($this->getCookieParams()[$name]); |
367 | } |
368 | |
369 | /** |
370 | * Get the relative path to $base. |
371 | * |
372 | * If $base is not given it will be determined by $_SERVER['SCRIPT_NAME']. |
373 | * |
374 | * e. g. The path is `/shop/products/foo` and the SCRIPT_NAME is `/shop/index.php` the result would be |
375 | * `/products/foo`. |
376 | * |
377 | * @param string $base The base where you want to start without slash |
378 | * @return string The path including the first slash |
379 | */ |
380 | public function getRelativePath(string $base = null): string |
381 | { |
382 | if ($base === null) { |
383 | $base = $this->getBase(); |
384 | } |
385 | |
386 | return substr($this->getUri()->getPath(), strlen(rtrim($base, '/'))); |
387 | } |
388 | |
389 | /** |
390 | * Get the domain absolute path to your application. |
391 | * |
392 | * If your software is installed on a separate (sub-)domain this will return '/' otherwise the path to your |
393 | * php file. |
394 | * |
395 | * @return string |
396 | */ |
397 | public function getBase(): string |
398 | { |
399 | return dirname($this->getServerParams()['SCRIPT_NAME'] ?? '/index.php'); |
400 | } |
401 | } |