Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
24 / 24
CRAP
100.00% covered (success)
100.00%
1 / 1
Request
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
24 / 24
32
100.00% covered (success)
100.00%
1 / 1
 postData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 input
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 has
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 hasPostData
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 path
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRequestTarget
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 withRequestTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withMethod
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withUri
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getServerParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCookieParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withCookieParams
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getQueryParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withQueryParams
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getUploadedFiles
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 withUploadedFiles
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 getParsedBody
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withParsedBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withAttribute
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 withoutAttribute
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Http;
6
7use InvalidArgumentException;
8use Nyholm\Psr7\UploadedFile;
9use Psr\Http\Message\ServerRequestInterface;
10use Psr\Http\Message\UploadedFileInterface;
11use Psr\Http\Message\UriInterface;
12use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile;
13use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
14
15class Request extends SymfonyRequest implements ServerRequestInterface
16{
17    use MessageTrait;
18
19    /**
20     * Get POST input
21     */
22    public function postData(string $key, mixed $default = null): mixed
23    {
24        return $this->request->get($key, $default);
25    }
26
27    /**
28     * Get input data
29     *
30     * @deprecated
31     */
32    public function input(string $key, mixed $default = null): mixed
33    {
34        return $this->get($key, $default);
35    }
36
37    /**
38     * Checks if the input exists
39     *
40     * @deprecated
41     */
42    public function has(string $key): bool
43    {
44        $value = $this->input($key);
45
46        return !is_null($value);
47    }
48
49    /**
50     * Get input from any request part (attributes, GET or POST)
51     *
52     * @deprecated
53     */
54    public function get(string $key, mixed $default = null): mixed
55    {
56        if ($this !== $result = $this->attributes->get($key, $this)) {
57            return $result;
58        }
59
60        if ($this->query->has($key)) {
61            return $this->query->all()[$key];
62        }
63
64        if ($this->request->has($key)) {
65            return $this->request->all()[$key];
66        }
67
68        return $default;
69    }
70
71    /**
72     * Checks if the POST data exists
73     */
74    public function hasPostData(string $key): bool
75    {
76        $value = $this->postData($key);
77
78        return !is_null($value);
79    }
80
81    /**
82     * Get the requested path
83     */
84    public function path(): string
85    {
86        $pattern = trim($this->getPathInfo(), '/');
87
88        return $pattern == '' ? '/' : $pattern;
89    }
90
91    /**
92     * Return the current URL
93     */
94    public function url(): string
95    {
96        return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
97    }
98
99    /**
100     * Retrieves the message's request target.
101     *
102     *
103     * Retrieves the message's request-target either as it will appear (for
104     * clients), as it appeared at request (for servers), or as it was
105     * specified for the instance (see withRequestTarget()).
106     *
107     * In most cases, this will be the origin-form of the composed URI,
108     * unless a value was provided to the concrete implementation (see
109     * withRequestTarget() below).
110     *
111     * If no URI is available, and no request-target has been specifically
112     * provided, this method MUST return the string "/".
113     */
114    public function getRequestTarget(): string
115    {
116        $query = $this->getQueryString();
117        return '/' . $this->path() . (!empty($query) ? '?' . $query : '');
118    }
119
120    /**
121     * Return an instance with the specific request-target.
122     *
123     * If the request needs a non-origin-form request-target â€” e.g., for
124     * specifying an absolute-form, authority-form, or asterisk-form â€”
125     * this method may be used to create an instance with the specified
126     * request-target, verbatim.
127     *
128     * This method MUST be implemented in such a way as to retain the
129     * immutability of the message, and MUST return an instance that has the
130     * changed request target.
131     *
132     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
133     *     request-target forms allowed in request messages)
134     * @return static
135     */
136    public function withRequestTarget(mixed $requestTarget): static
137    {
138        return $this->create($requestTarget);
139    }
140
141    /**
142     * Return an instance with the provided HTTP method.
143     *
144     * While HTTP method names are typically all uppercase characters, HTTP
145     * method names are case-sensitive and thus implementations SHOULD NOT
146     * modify the given string.
147     *
148     * This method MUST be implemented in such a way as to retain the
149     * immutability of the message, and MUST return an instance that has the
150     * changed request method.
151     *
152     * @param string $method Case-sensitive method.
153     * @return static
154     * @throws InvalidArgumentException for invalid HTTP methods.
155     */
156    public function withMethod(mixed $method): static
157    {
158        $new = clone $this;
159        $new->setMethod($method);
160
161        return $new;
162    }
163
164    /**
165     * Returns an instance with the provided URI.
166     *
167     * This method MUST update the Host header of the returned request by
168     * default if the URI contains a host component. If the URI does not
169     * contain a host component, any pre-existing Host header MUST be carried
170     * over to the returned request.
171     *
172     * You can opt-in to preserving the original state of the Host header by
173     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
174     * `true`, this method interacts with the Host header in the following ways:
175     *
176     * - If the Host header is missing or empty, and the new URI contains
177     *   a host component, this method MUST update the Host header in the returned
178     *   request.
179     * - If the Host header is missing or empty, and the new URI does not contain a
180     *   host component, this method MUST NOT update the Host header in the returned
181     *   request.
182     * - If a Host header is present and non-empty, this method MUST NOT update
183     *   the Host header in the returned request.
184     *
185     * This method MUST be implemented in such a way as to retain the
186     * immutability of the message, and MUST return an instance that has the
187     * new UriInterface instance.
188     *
189     * @link http://tools.ietf.org/html/rfc3986#section-4.3
190     * @param UriInterface $uri          New request URI to use.
191     * @param bool         $preserveHost Preserve the original state of the Host header.
192     * @return static
193     */
194    public function withUri(UriInterface $uri, mixed $preserveHost = false): static
195    {
196        $new = $this->create((string) $uri);
197        if ($preserveHost) {
198            $new->headers->set('HOST', $this->getHost());
199        }
200
201        return $new;
202    }
203
204    /**
205     * Retrieve server parameters.
206     *
207     * Retrieves data related to the incoming request environment,
208     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
209     * REQUIRED to originate from $_SERVER.
210     */
211    public function getServerParams(): array
212    {
213        return $this->server->all();
214    }
215
216    /**
217     * Retrieve cookies.
218     *
219     * Retrieves cookies sent by the client to the server.
220     *
221     * The data MUST be compatible with the structure of the $_COOKIE
222     * superglobal.
223     */
224    public function getCookieParams(): array
225    {
226        return $this->cookies->all();
227    }
228
229    /**
230     * Return an instance with the specified cookies.
231     *
232     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
233     * be compatible with the structure of $_COOKIE. Typically, this data will
234     * be injected at instantiation.
235     *
236     * This method MUST NOT update the related Cookie header of the request
237     * instance, nor related values in the server params.
238     *
239     * This method MUST be implemented in such a way as to retain the
240     * immutability of the message, and MUST return an instance that has the
241     * updated cookie values.
242     *
243     * @param array $cookies Array of key/value pairs representing cookies.
244     * @return static
245     */
246    public function withCookieParams(array $cookies): static
247    {
248        $new = clone $this;
249        $new->cookies = clone $this->cookies;
250        $new->cookies->replace($cookies);
251
252        return $new;
253    }
254
255    /**
256     * Retrieve query string arguments.
257     *
258     * Retrieves the deserialized query string arguments, if any.
259     *
260     * Note: the query params might not be in sync with the URI or server
261     * params. If you need to ensure you are only getting the original
262     * values, you may need to parse the query string from `getUri()->getQuery()`
263     * or from the `QUERY_STRING` server param.
264     */
265    public function getQueryParams(): array
266    {
267        return $this->query->all();
268    }
269
270    /**
271     * Return an instance with the specified query string arguments.
272     *
273     * These values SHOULD remain immutable over the course of the incoming
274     * request. They MAY be injected during instantiation, such as from PHP's
275     * $_GET superglobal, or MAY be derived from some other value such as the
276     * URI. In cases where the arguments are parsed from the URI, the data
277     * MUST be compatible with what PHP's parse_str() would return for
278     * purposes of how duplicate query parameters are handled, and how nested
279     * sets are handled.
280     *
281     * Setting query string arguments MUST NOT change the URI stored by the
282     * request, nor the values in the server params.
283     *
284     * This method MUST be implemented in such a way as to retain the
285     * immutability of the message, and MUST return an instance that has the
286     * updated query string arguments.
287     *
288     * @param array $query Array of query string arguments, typically from
289     *                     $_GET.
290     * @return static
291     */
292    public function withQueryParams(array $query): static
293    {
294        $new = clone $this;
295        $new->query = clone $this->query;
296        $new->query->replace($query);
297
298        return $new;
299    }
300
301    /**
302     * Retrieve normalized file upload data.
303     *
304     * This method returns upload metadata in a normalized tree, with each leaf
305     * an instance of Psr\Http\Message\UploadedFileInterface.
306     *
307     * These values MAY be prepared from $_FILES or the message body during
308     * instantiation, or MAY be injected via withUploadedFiles().
309     *
310     * @return array An array tree of UploadedFileInterface instances; an empty
311     *     array MUST be returned if no data is present.
312     */
313    public function getUploadedFiles(): array
314    {
315        $files = [];
316        /** @var SymfonyFile $file */
317        foreach ($this->files as $file) {
318            $files[] = new UploadedFile(
319                $file->getRealPath(),
320                $file->getSize(),
321                $file->getError(),
322                $file->getClientOriginalName(),
323                $file->getMimeType()
324            );
325        }
326
327        return $files;
328    }
329
330    /**
331     * Create a new instance with the specified uploaded files.
332     *
333     * This method MUST be implemented in such a way as to retain the
334     * immutability of the message, and MUST return an instance that has the
335     * updated body parameters.
336     *
337     * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
338     * @return static
339     * @throws InvalidArgumentException if an invalid structure is provided.
340     */
341    public function withUploadedFiles(array $uploadedFiles): static
342    {
343        $new = clone $this;
344        $new->files = clone $this->files;
345
346        $files = [];
347        foreach ($uploadedFiles as $file) {
348            /** @var UploadedFileInterface $file */
349            $filename = tempnam(sys_get_temp_dir(), 'upload');
350            $handle = fopen($filename, 'w');
351            fwrite($handle, $file->getStream()->getContents());
352            fclose($handle);
353
354            $files[] = new SymfonyFile(
355                $filename,
356                $file->getClientFilename(),
357                $file->getClientMediaType(),
358                $file->getError()
359            );
360        }
361        $new->files->add($files);
362
363        return $new;
364    }
365
366    /**
367     * Retrieve any parameters provided in the request body.
368     *
369     * If the request Content-Type is either application/x-www-form-urlencoded
370     * or multipart/form-data, and the request method is POST, this method MUST
371     * return the contents of $_POST.
372     *
373     * Otherwise, this method may return any results of deserializing
374     * the request body content; as parsing returns structured content, the
375     * potential types MUST be arrays or objects only. A null value indicates
376     * the absence of body content.
377     *
378     * @return null|array|object The deserialized body parameters, if any.
379     *     These will typically be an array or object.
380     */
381    public function getParsedBody(): array|object|null
382    {
383        return $this->request->all();
384    }
385
386    /**
387     * Return an instance with the specified body parameters.
388     *
389     * These MAY be injected during instantiation.
390     *
391     * If the request Content-Type is either application/x-www-form-urlencoded
392     * or multipart/form-data, and the request method is POST, use this method
393     * ONLY to inject the contents of $_POST.
394     *
395     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
396     * deserializing the request body content. Deserialization/parsing returns
397     * structured data, and, as such, this method ONLY accepts arrays or objects,
398     * or a null value if nothing was available to parse.
399     *
400     * As an example, if content negotiation determines that the request data
401     * is a JSON payload, this method could be used to create a request
402     * instance with the deserialized parameters.
403     *
404     * This method MUST be implemented in such a way as to retain the
405     * immutability of the message, and MUST return an instance that has the
406     * updated body parameters.
407     *
408     * @param null|array|object $data The deserialized body data. This will
409     *                                typically be in an array or object.
410     * @return static
411     * @throws InvalidArgumentException if an unsupported argument type is
412     *                                provided.
413     */
414    public function withParsedBody(mixed $data): static
415    {
416        $new = clone $this;
417        $new->request = clone $this->request;
418
419        $new->request->replace($data);
420
421        return $new;
422    }
423
424    /**
425     * Retrieve attributes derived from the request.
426     *
427     * The request "attributes" may be used to allow injection of any
428     * parameters derived from the request: e.g., the results of path
429     * match operations; the results of decrypting cookies; the results of
430     * deserializing non-form-encoded message bodies; etc. Attributes
431     * will be application and request specific, and CAN be mutable.
432     *
433     * @return array Attributes derived from the request.
434     */
435    public function getAttributes(): array
436    {
437        return $this->attributes->all();
438    }
439
440    /**
441     * Retrieve a single derived request attribute.
442     *
443     * Retrieves a single derived request attribute as described in
444     * getAttributes(). If the attribute has not been previously set, returns
445     * the default value as provided.
446     *
447     * This method obviates the need for a hasAttribute() method, as it allows
448     * specifying a default value to return if the attribute is not found.
449     *
450     * @param string $name    The attribute name.
451     * @param mixed  $default Default value to return if the attribute does not exist.
452     * @see getAttributes()
453     */
454    public function getAttribute(mixed $name, mixed $default = null): mixed
455    {
456        return $this->attributes->get($name, $default);
457    }
458
459    /**
460     * Return an instance with the specified derived request attribute.
461     *
462     * This method allows setting a single derived request attribute as
463     * described in getAttributes().
464     *
465     * This method MUST be implemented in such a way as to retain the
466     * immutability of the message, and MUST return an instance that has the
467     * updated attribute.
468     *
469     * @param string $name  The attribute name.
470     * @param mixed  $value The value of the attribute.
471     * @return static
472     * @see getAttributes()
473     */
474    public function withAttribute(mixed $name, mixed $value): static
475    {
476        $new = clone $this;
477        $new->attributes = clone $this->attributes;
478
479        $new->attributes->set($name, $value);
480
481        return $new;
482    }
483
484    /**
485     * Return an instance that removes the specified derived request attribute.
486     *
487     * This method allows removing a single derived request attribute as
488     * described in getAttributes().
489     *
490     * This method MUST be implemented in such a way as to retain the
491     * immutability of the message, and MUST return an instance that removes
492     * the attribute.
493     *
494     * @param string $name The attribute name.
495     * @return static
496     * @see getAttributes()
497     */
498    public function withoutAttribute(mixed $name): static
499    {
500        $new = clone $this;
501        $new->attributes = clone $this->attributes;
502
503        $new->attributes->remove($name);
504
505        return $new;
506    }
507}