Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
103 / 103
96.34% covered (success)
96.34%
79 / 82
64.29% covered (warning)
64.29%
45 / 70
93.94% covered (success)
93.94%
31 / 33
CRAP
0.00% covered (danger)
0.00%
0 / 1
Request
100.00% covered (success)
100.00%
103 / 103
96.34% covered (success)
96.34%
79 / 82
64.29% covered (warning)
64.29%
45 / 70
100.00% covered (success)
100.00%
33 / 33
192.80
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unwrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wrap
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 params
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 param
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 form
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 field
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 cookies
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cookie
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 serverParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 server
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 header
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 headerArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 headers
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 attributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 uri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 origin
100.00% covered (success)
100.00%
6 / 6
71.43% covered (warning)
71.43%
5 / 7
25.00% covered (danger)
25.00%
1 / 4
100.00% covered (success)
100.00%
1 / 1
6.80
 target
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 method
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 body
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 json
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 files
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
13 / 13
36.36% covered (danger)
36.36%
4 / 11
100.00% covered (success)
100.00%
1 / 1
15.28
 file
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
13 / 13
57.14% covered (warning)
57.14%
4 / 7
100.00% covered (success)
100.00%
1 / 1
8.83
 returnOrFail
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
9 / 9
25.00% covered (danger)
25.00%
3 / 12
100.00% covered (success)
100.00%
1 / 1
15.55
 formatKeys
100.00% covered (success)
100.00%
4 / 4
75.00% covered (warning)
75.00%
3 / 4
50.00% covered (danger)
50.00%
1 / 2
100.00% covered (success)
100.00%
1 / 1
1.12
 validateKeys
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
7 / 7
66.67% covered (warning)
66.67%
4 / 6
100.00% covered (success)
100.00%
1 / 1
4.59
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Core;
6
7use Celemas\Core\Exception\OutOfBoundsException;
8use Celemas\Core\Exception\RuntimeException;
9use Celemas\Router\RequestWrapper;
10use Override;
11use Psr\Http\Message\ServerRequestInterface as PsrServerRequest;
12use Psr\Http\Message\StreamInterface as PsrStream;
13use Psr\Http\Message\UploadedFileInterface as PsrUploadedFile;
14use Psr\Http\Message\UriInterface as PsrUri;
15
16/** @api */
17class Request implements RequestWrapper
18{
19    public function __construct(
20        protected PsrServerRequest $psrRequest,
21    ) {}
22
23    #[Override]
24    public function unwrap(): PsrServerRequest
25    {
26        return $this->psrRequest;
27    }
28
29    public function wrap(PsrServerRequest $request): static
30    {
31        $this->psrRequest = $request;
32
33        return $this;
34    }
35
36    public function params(): array
37    {
38        return $this->psrRequest->getQueryParams();
39    }
40
41    public function param(string $key, mixed $default = null): mixed
42    {
43        $params = $this->psrRequest->getQueryParams();
44        $error = 'Query string variable not found';
45
46        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
47    }
48
49    public function form(): ?array
50    {
51        $body = $this->psrRequest->getParsedBody();
52        assert($body === null || is_array($body), 'Parsed form body must be null or an array');
53
54        return $body;
55    }
56
57    public function field(string $key, mixed $default = null): mixed
58    {
59        $body = $this->psrRequest->getParsedBody();
60        assert($body === null || is_array($body), 'Parsed form body must be null or an array');
61        $error = 'Form field not found';
62
63        return $this->returnOrFail($body, $key, $default, $error, func_num_args());
64    }
65
66    public function cookies(): array
67    {
68        return $this->psrRequest->getCookieParams();
69    }
70
71    public function cookie(string $key, mixed $default = null): mixed
72    {
73        $params = $this->psrRequest->getCookieParams();
74        $error = 'Cookie not found';
75
76        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
77    }
78
79    public function serverParams(): array
80    {
81        return $this->psrRequest->getServerParams();
82    }
83
84    public function server(string $key, mixed $default = null): mixed
85    {
86        $params = $this->psrRequest->getServerParams();
87        $error = 'Server parameter not found';
88
89        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
90    }
91
92    public function header(string $name): string
93    {
94        return $this->psrRequest->getHeaderLine($name);
95    }
96
97    public function headerArray(string $header): array
98    {
99        return $this->psrRequest->getHeader($header);
100    }
101
102    public function headers(bool $firstOnly = false): array
103    {
104        $headers = $this->psrRequest->getHeaders();
105
106        if ($firstOnly) {
107            return array_combine(
108                array_keys($headers),
109                array_map(static fn(array $val): string => $val[0], $headers),
110            );
111        }
112
113        return $headers;
114    }
115
116    public function setHeader(string $header, string $value): static
117    {
118        $this->psrRequest = $this->psrRequest->withHeader($header, $value);
119
120        return $this;
121    }
122
123    public function addHeader(string $header, string $value): static
124    {
125        $this->psrRequest = $this->psrRequest->withAddedHeader($header, $value);
126
127        return $this;
128    }
129
130    public function removeHeader(string $header): static
131    {
132        $this->psrRequest = $this->psrRequest->withoutHeader($header);
133
134        return $this;
135    }
136
137    public function hasHeader(string $header): bool
138    {
139        return $this->psrRequest->hasHeader($header);
140    }
141
142    public function attributes(): array
143    {
144        return $this->psrRequest->getAttributes();
145    }
146
147    public function set(string $attribute, mixed $value): static
148    {
149        $this->psrRequest = $this->psrRequest->withAttribute($attribute, $value);
150
151        return $this;
152    }
153
154    public function get(string $key, mixed $default = null): mixed
155    {
156        $params = $this->psrRequest->getAttributes();
157        $error = 'Request attribute not found';
158
159        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
160    }
161
162    public function uri(): PsrUri
163    {
164        return $this->psrRequest->getUri();
165    }
166
167    public function origin(): string
168    {
169        $uri = $this->psrRequest->getUri();
170        $scheme = $uri->getScheme();
171        $origin = $scheme ? $scheme . ':' : '';
172        $authority = $uri->getAuthority();
173        $origin .= $authority ? '//' . $authority : '';
174
175        return $origin;
176    }
177
178    public function target(): string
179    {
180        return $this->psrRequest->getRequestTarget();
181    }
182
183    public function method(): string
184    {
185        return strtoupper($this->psrRequest->getMethod());
186    }
187
188    public function isMethod(string $method): bool
189    {
190        return strtoupper($method) === $this->method();
191    }
192
193    public function body(): PsrStream
194    {
195        return $this->psrRequest->getBody();
196    }
197
198    public function json(
199        int $flags = JSON_OBJECT_AS_ARRAY,
200    ): mixed {
201        $body = (string) $this->psrRequest->getBody();
202
203        return json_decode(
204            $body,
205            true,
206            512, // PHP default value
207            $flags,
208        );
209    }
210
211    /**
212     * Returns always a list of uploaded files, even if there is
213     * only one file.
214     *
215     * Psalm does not support multi file uploads yet and complains
216     * about type issues. We need to suppres some of these errors.
217     *
218     * @no-named-arguments
219     *
220     * @param list<string>|string ...$keys
221     *
222     * @throws OutOfBoundsException RuntimeException
223     */
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
230            return $files;
231        }
232
233        // Walk into the uploaded files structure
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
251            return [$files];
252        }
253
254        assert(is_array($files), 'Uploaded files selection must resolve to an array');
255
256        return $files;
257    }
258
259    /**
260     * Psalm does not support multi file uploads yet and complains
261     * about type issues. We need to suppres some of the errors.
262     *
263     * @no-named-arguments
264     *
265     * @param list<non-empty-string>|string ...$keys
266     *
267     * @throws OutOfBoundsException RuntimeException
268     */
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
274            throw new RuntimeException('No file key given');
275        }
276
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
281            if (isset($files[$key])) {
282                /** @var array|PsrUploadedFile $files */
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
287                    if ($i < count($keys)) {
288                        throw new OutOfBoundsException(
289                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
290                        );
291                    }
292
293                    return $files;
294                }
295            } else {
296                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
297            }
298        }
299
300        throw new RuntimeException('Multiple files available at key ' . $this->formatKeys($keys));
301    }
302
303    private function returnOrFail(
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
311            return $default;
312        }
313
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
317            return $array[$key];
318        }
319
320        throw new OutOfBoundsException("{$error}: '{$key}'");
321    }
322
323    /** @param non-empty-list<string> $keys */
324    private function formatKeys(array $keys): string
325    {
326        return implode('', array_map(
327            static fn($key) => "['" . $key . "']",
328            $keys,
329        ));
330    }
331
332    /**
333     * @param list<list<string>|string> $keys
334     *
335     * @return list<string>
336     */
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
340            if (count($keys) > 1) {
341                throw new RuntimeException('Either provide a single array or plain string arguments');
342            }
343            $keys = $keys[0];
344        }
345
346        /** @var list<string> $keys */
347        return $keys;
348    }
349}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Request->__construct
20        protected PsrServerRequest $psrRequest,
21    ) {}
Request->addHeader
123    public function addHeader(string $header, string $value): static
124    {
125        $this->psrRequest = $this->psrRequest->withAddedHeader($header, $value);
126
127        return $this;
128    }
Request->attributes
144        return $this->psrRequest->getAttributes();
145    }
Request->body
195        return $this->psrRequest->getBody();
196    }
Request->cookie
71    public function cookie(string $key, mixed $default = null): mixed
72    {
73        $params = $this->psrRequest->getCookieParams();
74        $error = 'Cookie not found';
75
76        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
77    }
Request->cookies
68        return $this->psrRequest->getCookieParams();
69    }
Request->field
57    public function field(string $key, mixed $default = null): mixed
58    {
59        $body = $this->psrRequest->getParsedBody();
60        assert($body === null || is_array($body), 'Parsed form body must be null or an array');
61        $error = 'Form field not found';
62
63        return $this->returnOrFail($body, $key, $default, $error, func_num_args());
64    }
Request->file
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
274            throw new RuntimeException('No file key given');
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
 
281            if (isset($files[$key])) {
 
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
 
287                    if ($i < count($keys)) {
 
288                        throw new OutOfBoundsException(
289                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
 
281            if (isset($files[$key])) {
 
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
 
287                    if ($i < count($keys)) {
 
293                    return $files;
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
 
281            if (isset($files[$key])) {
 
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
 
281            if (isset($files[$key])) {
 
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
281            if (isset($files[$key])) {
282                /** @var array|PsrUploadedFile $files */
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
287                    if ($i < count($keys)) {
288                        throw new OutOfBoundsException(
289                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
290                        );
291                    }
292
293                    return $files;
294                }
295            } else {
296                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
297            }
298        }
299
300        throw new RuntimeException('Multiple files available at key ' . $this->formatKeys($keys));
301    }
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
 
281            if (isset($files[$key])) {
 
296                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
281            if (isset($files[$key])) {
282                /** @var array|PsrUploadedFile $files */
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
287                    if ($i < count($keys)) {
288                        throw new OutOfBoundsException(
289                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
290                        );
291                    }
292
293                    return $files;
294                }
295            } else {
296                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
297            }
298        }
299
300        throw new RuntimeException('Multiple files available at key ' . $this->formatKeys($keys));
301    }
269    public function file(array|string ...$keys): PsrUploadedFile
270    {
271        $keys = $this->validateKeys($keys);
272
273        if (count($keys) === 0) {
 
277        $files = $this->psrRequest->getUploadedFiles();
278        $i = 0;
279
280        foreach ($keys as $key) {
 
280        foreach ($keys as $key) {
281            if (isset($files[$key])) {
282                /** @var array|PsrUploadedFile $files */
283                $files = $files[$key];
284                $i++;
285
286                if ($files instanceof PsrUploadedFile) {
287                    if ($i < count($keys)) {
288                        throw new OutOfBoundsException(
289                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
290                        );
291                    }
292
293                    return $files;
294                }
295            } else {
296                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
297            }
298        }
299
300        throw new RuntimeException('Multiple files available at key ' . $this->formatKeys($keys));
301    }
Request->files
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
230            return $files;
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
251            return [$files];
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
254        assert(is_array($files), 'Uploaded files selection must resolve to an array');
255
256        return $files;
257    }
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
251            return [$files];
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
254        assert(is_array($files), 'Uploaded files selection must resolve to an array');
255
256        return $files;
257    }
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
235            if (is_array($files) && array_key_exists($key, $files)) {
 
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
251            return [$files];
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
254        assert(is_array($files), 'Uploaded files selection must resolve to an array');
255
256        return $files;
257    }
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
251            return [$files];
224    public function files(array|string ...$keys): array
225    {
226        $files = $this->psrRequest->getUploadedFiles();
227        $keys = $this->validateKeys($keys);
228
229        if (count($keys) === 0) {
 
234        foreach ($keys as $key) {
 
234        foreach ($keys as $key) {
235            if (is_array($files) && array_key_exists($key, $files)) {
236                /**
237                 * @psalm-suppress MixedAssignment
238                 *
239                 * Psalm does not support recursive types like:
240                 *     T = array<string, string|T>
241                 */
242                $files = $files[$key];
243            } else {
244                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
245            }
246        }
247
248        // Check if it is a single file upload.
249        // A multifile upload would already produce an array
250        if ($files instanceof PsrUploadedFile) {
 
254        assert(is_array($files), 'Uploaded files selection must resolve to an array');
255
256        return $files;
257    }
Request->form
51        $body = $this->psrRequest->getParsedBody();
52        assert($body === null || is_array($body), 'Parsed form body must be null or an array');
53
54        return $body;
55    }
Request->formatKeys
324    private function formatKeys(array $keys): string
325    {
326        return implode('', array_map(
 
326        return implode('', array_map(
 
327            static fn($key) => "['" . $key . "']",
328            $keys,
329        ));
330    }
324    private function formatKeys(array $keys): string
325    {
326        return implode('', array_map(
 
326        return implode('', array_map(
327            static fn($key) => "['" . $key . "']",
 
327            static fn($key) => "['" . $key . "']",
328            $keys,
329        ));
330    }
Request->get
154    public function get(string $key, mixed $default = null): mixed
155    {
156        $params = $this->psrRequest->getAttributes();
157        $error = 'Request attribute not found';
158
159        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
160    }
Request->hasHeader
137    public function hasHeader(string $header): bool
138    {
139        return $this->psrRequest->hasHeader($header);
140    }
Request->header
92    public function header(string $name): string
93    {
94        return $this->psrRequest->getHeaderLine($name);
95    }
Request->headerArray
97    public function headerArray(string $header): array
98    {
99        return $this->psrRequest->getHeader($header);
100    }
Request->headers
102    public function headers(bool $firstOnly = false): array
103    {
104        $headers = $this->psrRequest->getHeaders();
105
106        if ($firstOnly) {
 
107            return array_combine(
108                array_keys($headers),
109                array_map(static fn(array $val): string => $val[0], $headers),
102    public function headers(bool $firstOnly = false): array
103    {
104        $headers = $this->psrRequest->getHeaders();
105
106        if ($firstOnly) {
 
113        return $headers;
114    }
Request->isMethod
188    public function isMethod(string $method): bool
189    {
190        return strtoupper($method) === $this->method();
191    }
Request->json
199        int $flags = JSON_OBJECT_AS_ARRAY,
200    ): mixed {
201        $body = (string) $this->psrRequest->getBody();
202
203        return json_decode(
204            $body,
205            true,
206            512, // PHP default value
207            $flags,
208        );
209    }
Request->method
185        return strtoupper($this->psrRequest->getMethod());
186    }
Request->origin
169        $uri = $this->psrRequest->getUri();
170        $scheme = $uri->getScheme();
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
172        $authority = $uri->getAuthority();
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
174
175        return $origin;
176    }
169        $uri = $this->psrRequest->getUri();
170        $scheme = $uri->getScheme();
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
172        $authority = $uri->getAuthority();
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
174
175        return $origin;
176    }
169        $uri = $this->psrRequest->getUri();
170        $scheme = $uri->getScheme();
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
172        $authority = $uri->getAuthority();
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
174
175        return $origin;
176    }
169        $uri = $this->psrRequest->getUri();
170        $scheme = $uri->getScheme();
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
 
171        $origin = $scheme ? $scheme . ':' : '';
172        $authority = $uri->getAuthority();
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
 
173        $origin .= $authority ? '//' . $authority : '';
174
175        return $origin;
176    }
Request->param
41    public function param(string $key, mixed $default = null): mixed
42    {
43        $params = $this->psrRequest->getQueryParams();
44        $error = 'Query string variable not found';
45
46        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
47    }
Request->params
38        return $this->psrRequest->getQueryParams();
39    }
Request->removeHeader
130    public function removeHeader(string $header): static
131    {
132        $this->psrRequest = $this->psrRequest->withoutHeader($header);
133
134        return $this;
135    }
Request->returnOrFail
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
311            return $default;
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
317            return $array[$key];
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
320        throw new OutOfBoundsException("{$error}: '{$key}'");
321    }
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
311            return $default;
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
317            return $array[$key];
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
320        throw new OutOfBoundsException("{$error}: '{$key}'");
321    }
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
311            return $default;
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
317            return $array[$key];
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
320        throw new OutOfBoundsException("{$error}: '{$key}'");
321    }
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
311            return $default;
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
317            return $array[$key];
304        ?array $array,
305        string $key,
306        mixed $default,
307        string $error,
308        int $numArgs,
309    ): mixed {
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
310        if (($array === null || !array_key_exists($key, $array)) && $numArgs > 1) {
 
314        assert($array !== null, 'Input array must not be null when no default value is provided');
315
316        if (array_key_exists($key, $array)) {
 
320        throw new OutOfBoundsException("{$error}: '{$key}'");
321    }
Request->server
84    public function server(string $key, mixed $default = null): mixed
85    {
86        $params = $this->psrRequest->getServerParams();
87        $error = 'Server parameter not found';
88
89        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
90    }
Request->serverParams
81        return $this->psrRequest->getServerParams();
82    }
Request->set
147    public function set(string $attribute, mixed $value): static
148    {
149        $this->psrRequest = $this->psrRequest->withAttribute($attribute, $value);
150
151        return $this;
152    }
Request->setHeader
116    public function setHeader(string $header, string $value): static
117    {
118        $this->psrRequest = $this->psrRequest->withHeader($header, $value);
119
120        return $this;
121    }
Request->target
180        return $this->psrRequest->getRequestTarget();
181    }
Request->unwrap
26        return $this->psrRequest;
27    }
Request->uri
164        return $this->psrRequest->getUri();
165    }
Request->validateKeys
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
340            if (count($keys) > 1) {
 
341                throw new RuntimeException('Either provide a single array or plain string arguments');
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
340            if (count($keys) > 1) {
 
343            $keys = $keys[0];
344        }
345
346        /** @var list<string> $keys */
347        return $keys;
 
347        return $keys;
348    }
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
347        return $keys;
348    }
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
340            if (count($keys) > 1) {
 
341                throw new RuntimeException('Either provide a single array or plain string arguments');
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
340            if (count($keys) > 1) {
 
343            $keys = $keys[0];
344        }
345
346        /** @var list<string> $keys */
347        return $keys;
 
347        return $keys;
348    }
337    private function validateKeys(array $keys): array
338    {
339        if (isset($keys[0]) && is_array($keys[0])) {
 
339        if (isset($keys[0]) && is_array($keys[0])) {
 
347        return $keys;
348    }
Request->wrap
29    public function wrap(PsrServerRequest $request): static
30    {
31        $this->psrRequest = $request;
32
33        return $this;
34    }
{closure:/workspace/celemas/core/src/Request.php:109-109}
109                array_map(static fn(array $val): string => $val[0], $headers),
{closure:/workspace/celemas/core/src/Request.php:327-327}
327            static fn($key) => "['" . $key . "']",