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

Branches

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

Request->__construct
26        protected readonly PsrServerRequest $psrRequest,
27    ) {}
Request->attributes
137        return $this->psrRequest->getAttributes();
138    }
Request->body
187        return $this->psrRequest->getBody();
188    }
Request->cookie
69    public function cookie(string $key, mixed $default = null): mixed
70    {
71        $params = $this->psrRequest->getCookieParams();
72        $error = 'Cookie not found';
73
74        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
75    }
Request->cookies
66        return $this->psrRequest->getCookieParams();
67    }
Request->field
55    public function field(string $key, mixed $default = null): mixed
56    {
57        $body = $this->parsedBody();
58        $error = 'Form field not found';
59
60        return $this->returnOrFail($body, $key, $default, $error, func_num_args());
61    }
Request->file
248    public function file(array|string ...$keys): PsrUploadedFile
249    {
250        $keys = $this->validateKeys($keys);
251
252        if (count($keys) === 0) {
253            throw new RuntimeException('No file key given');
256        $files = $this->psrRequest->getUploadedFiles();
257        $i = 0;
258
259        foreach ($keys as $key) {
259        foreach ($keys as $key) {
260            if (isset($files[$key])) {
262                $files = $files[$key];
263                $i++;
264
265                if ($files instanceof PsrUploadedFile) {
266                    if ($i < count($keys)) {
267                        throw new OutOfBoundsException(
268                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
272                    return $files;
260            if (isset($files[$key])) {
275                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
259        foreach ($keys as $key) {
259        foreach ($keys as $key) {
260            if (isset($files[$key])) {
261                /** @var array<array-key, mixed>|PsrUploadedFile $files */
262                $files = $files[$key];
263                $i++;
264
265                if ($files instanceof PsrUploadedFile) {
266                    if ($i < count($keys)) {
267                        throw new OutOfBoundsException(
268                            'Invalid file key (too deep) ' . $this->formatKeys($keys),
269                        );
270                    }
271
272                    return $files;
273                }
274            } else {
275                throw new OutOfBoundsException('Invalid file key ' . $this->formatKeys($keys));
276            }
277        }
278
279        throw new RuntimeException('Multiple files available at key ' . $this->formatKeys($keys));
280    }
Request->files
210    public function files(array|string ...$keys): array
211    {
212        $files = $this->psrRequest->getUploadedFiles();
213        $keys = $this->validateKeys($keys);
214
215        // Walk into the uploaded files structure
216        foreach ($keys as $key) {
216        foreach ($keys as $key) {
217            if (is_array($files) && array_key_exists($key, $files)) {
217            if (is_array($files) && array_key_exists($key, $files)) {
217            if (is_array($files) && array_key_exists($key, $files)) {
217            if (is_array($files) && array_key_exists($key, $files)) {
218                /**
219                 * @psalm-suppress MixedAssignment
220                 *
221                 * Psalm does not support recursive types like:
222                 *     T = array<string, string|T>
223                 */
224                $files = $files[$key];
226                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
216        foreach ($keys as $key) {
216        foreach ($keys as $key) {
217            if (is_array($files) && array_key_exists($key, $files)) {
218                /**
219                 * @psalm-suppress MixedAssignment
220                 *
221                 * Psalm does not support recursive types like:
222                 *     T = array<string, string|T>
223                 */
224                $files = $files[$key];
225            } else {
226                throw new OutOfBoundsException('Invalid files key ' . $this->formatKeys($keys));
227            }
228        }
229
230        // Check if it is a single file upload.
231        // A multifile upload would already produce an array
232        if ($files instanceof PsrUploadedFile) {
233            return [$files];
236        assert(is_array($files), 'Uploaded files selection must resolve to an array');
237
238        return $files;
239    }
Request->form
52        return $this->parsedBody();
53    }
Request->formatKeys
314    private function formatKeys(array $keys): string
315    {
316        return implode('', array_map(
316        return implode('', array_map(
316        return implode('', array_map(
317            static fn(string $key): string => "['" . $key . "']",
317            static fn(string $key): string => "['" . $key . "']",
318            $keys,
319        ));
320    }
Request->get
146    public function get(string $key, mixed $default = null): mixed
147    {
148        $params = $this->psrRequest->getAttributes();
149        $error = 'Request attribute not found';
150
151        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
152    }
Request->hasHeader
129    public function hasHeader(string $header): bool
130    {
131        return $this->psrRequest->hasHeader($header);
132    }
Request->header
91    public function header(string $name): string
92    {
93        return $this->psrRequest->getHeaderLine($name);
94    }
Request->headerArray
97    public function headerArray(string $header): array
98    {
99        return $this->psrRequest->getHeader($header);
100    }
Request->headers
103    public function headers(bool $firstOnly = false): array
104    {
105        $headers = $this->psrRequest->getHeaders();
106
107        if ($firstOnly) {
108            return array_map(static fn(array $val): string => $val[0] ?? '', $headers);
111        return $headers;
112    }
Request->isMethod
180    public function isMethod(string $method): bool
181    {
182        return strtoupper($method) === $this->method();
183    }
Request->json
191        int $flags = JSON_OBJECT_AS_ARRAY,
192    ): mixed {
193        $body = (string) $this->psrRequest->getBody();
194
195        return json_decode($body, true, flags: $flags);
196    }
Request->method
177        return strtoupper($this->psrRequest->getMethod());
178    }
Request->origin
161        $uri = $this->psrRequest->getUri();
162        $scheme = $uri->getScheme();
163        $origin = $scheme !== '' ? $scheme . ':' : '';
163        $origin = $scheme !== '' ? $scheme . ':' : '';
163        $origin = $scheme !== '' ? $scheme . ':' : '';
163        $origin = $scheme !== '' ? $scheme . ':' : '';
164        $authority = $uri->getAuthority();
165        $origin .= $authority !== '' ? '//' . $authority : '';
165        $origin .= $authority !== '' ? '//' . $authority : '';
165        $origin .= $authority !== '' ? '//' . $authority : '';
165        $origin .= $authority !== '' ? '//' . $authority : '';
166
167        return $origin;
168    }
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->parsedBody
285        $body = $this->psrRequest->getParsedBody();
286
287        if (is_object($body)) {
288            throw new RuntimeException('The parsed body is an object, not form data');
291        return $body;
292    }
Request->returnOrFail
296        ?array $array,
297        string $key,
298        mixed $default,
299        string $error,
300        int $numArgs,
301    ): mixed {
302        if ($array !== null && array_key_exists($key, $array)) {
302        if ($array !== null && array_key_exists($key, $array)) {
302        if ($array !== null && array_key_exists($key, $array)) {
303            return $array[$key];
306        if ($numArgs > 1) {
307            return $default;
310        throw new OutOfBoundsException("{$error}: '{$key}'");
311    }
Request->server
83    public function server(string $key, mixed $default = null): mixed
84    {
85        $params = $this->psrRequest->getServerParams();
86        $error = 'Server parameter not found';
87
88        return $this->returnOrFail($params, $key, $default, $error, func_num_args());
89    }
Request->serverParams
80        return $this->psrRequest->getServerParams();
81    }
Request->target
172        return $this->psrRequest->getRequestTarget();
173    }
Request->unwrap
32        return $this->psrRequest;
33    }
Request->uri
156        return $this->psrRequest->getUri();
157    }
Request->validateKeys
327    private function validateKeys(array $keys): array
328    {
329        if (count($keys) === 1 && is_array($keys[0])) {
329        if (count($keys) === 1 && is_array($keys[0])) {
329        if (count($keys) === 1 && is_array($keys[0])) {
330            $keys = $keys[0];
331        }
332
333        $strings = [];
333        $strings = [];
334
335        foreach ($keys as $key) {
335        foreach ($keys as $key) {
336            if (is_array($key)) {
337                throw new RuntimeException('Either provide a single array or plain string arguments');
335        foreach ($keys as $key) {
336            if (is_array($key)) {
337                throw new RuntimeException('Either provide a single array or plain string arguments');
338            }
339
340            $strings[] = $key;
335        foreach ($keys as $key) {
336            if (is_array($key)) {
337                throw new RuntimeException('Either provide a single array or plain string arguments');
338            }
339
340            $strings[] = $key;
341        }
342
343        return $strings;
344    }
Request->with
141    public function with(string $attribute, mixed $value): static
142    {
143        return $this->wrapping($this->psrRequest->withAttribute($attribute, $value));
144    }
Request->withAddedHeader
119    public function withAddedHeader(string $header, string $value): static
120    {
121        return $this->wrapping($this->psrRequest->withAddedHeader($header, $value));
122    }
Request->withHeader
114    public function withHeader(string $header, string $value): static
115    {
116        return $this->wrapping($this->psrRequest->withHeader($header, $value));
117    }
Request->withoutHeader
124    public function withoutHeader(string $header): static
125    {
126        return $this->wrapping($this->psrRequest->withoutHeader($header));
127    }
Request->wrapping
347    private function wrapping(PsrServerRequest $request): static
348    {
349        return clone($this, ['psrRequest' => $request]);
350    }
{closure:/workspace/celema/core/src/Request.php:108-108}
108            return array_map(static fn(array $val): string => $val[0] ?? '', $headers);
{closure:/workspace/celema/core/src/Request.php:317-317}
317            static fn(string $key): string => "['" . $key . "']",