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