ServerRequest::withBody()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 4
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sunrise\Http\Router;
15
16
use Generator;
17
use LogicException;
18
use Psr\Http\Message\ServerRequestInterface;
19
use Psr\Http\Message\StreamInterface;
20
use Psr\Http\Message\UriInterface;
21
use Sunrise\Coder\MediaTypeInterface;
22
use Sunrise\Http\Router\Dictionary\HeaderName;
23
use Sunrise\Http\Router\Helper\HeaderParser;
24
25
use function extension_loaded;
26
use function reset;
27
use function strcasecmp;
28
use function usort;
29
30
/**
31
 * @since 3.0.0
32
 */
33
final class ServerRequest implements ServerRequestInterface
34
{
35 144
    public function __construct(
36
        private ServerRequestInterface $request,
37
    ) {
38 144
    }
39
40 144
    public static function create(ServerRequestInterface $request): self
41
    {
42 144
        return ($request instanceof self) ? $request : new self($request);
43
    }
44
45 2
    public function hasRoute(): bool
46
    {
47 2
        $route = $this->request->getAttribute(RouteInterface::class);
48
49 2
        return $route instanceof RouteInterface;
50
    }
51
52
    /**
53
     * @throws LogicException If the request doesn't contain a route.
54
     */
55 38
    public function getRoute(): RouteInterface
56
    {
57 38
        $route = $this->request->getAttribute(RouteInterface::class);
58
59 38
        if (! $route instanceof RouteInterface) {
60
            // phpcs:ignore Generic.Files.LineLength.TooLong
61 1
            throw new LogicException('At this level of the application, the request does not contain information about the requested route.');
62
        }
63
64 37
        return $route;
65
    }
66
67 13
    public function getClientProducedMediaType(): ?MediaTypeInterface
68
    {
69 13
        $values = HeaderParser::parseHeader($this->request->getHeaderLine(HeaderName::CONTENT_TYPE));
70 13
        if ($values === []) {
71 2
            return null;
72
        }
73
74 11
        [$identifier] = reset($values);
75
76 11
        return new MediaType($identifier);
77
    }
78
79
    /**
80
     * @return Generator<int, MediaTypeInterface>
81
     */
82 26
    public function getClientConsumedMediaTypes(): Generator
83
    {
84 26
        $values = HeaderParser::parseHeader($this->request->getHeaderLine(HeaderName::ACCEPT));
85 26
        if ($values === []) {
86 2
            return;
87
        }
88
89
        // https://github.com/php/php-src/blob/d9549d2ee215cb04aa5d2e3195c608d581fb272c/ext/standard/array.c#L900-L903
90 24
        usort($values, static fn(array $a, array $b): int => (
91 15
            (float) ($b[1]['q'] ?? '1') <=> (float) ($a[1]['q'] ?? '1')
92 24
        ));
93
94 24
        foreach ($values as [$identifier]) {
95 24
            yield new MediaType($identifier);
96
        }
97
    }
98
99
    /**
100
     * @param T ...$serverProducedMediaTypes
101
     *
102
     * @return T|null
103
     *
104
     * @template T of MediaTypeInterface
105
     */
106 27
    public function getClientPreferredMediaType(MediaTypeInterface ...$serverProducedMediaTypes): ?MediaTypeInterface
107
    {
108 27
        if ($serverProducedMediaTypes === []) {
109 9
            return null;
110
        }
111
112 18
        foreach ($this->getClientConsumedMediaTypes() as $clientConsumedMediaType) {
113 17
            foreach ($serverProducedMediaTypes as $serverProducedMediaType) {
114
                if (
115 17
                    strcasecmp(
116 17
                        $clientConsumedMediaType->getIdentifier(),
117 17
                        $serverProducedMediaType->getIdentifier(),
118 17
                    ) === 0
119
                ) {
120 16
                    return $serverProducedMediaType;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $serverProducedMediaType returns the type Sunrise\Coder\MediaTypeInterface which is incompatible with the documented return type Sunrise\Http\Router\T|null.
Loading history...
121
                }
122
            }
123
        }
124
125 2
        return null;
126
    }
127
128 13
    public function serverConsumesMediaType(MediaTypeInterface $clientProducedMediaType): bool
129
    {
130 13
        $serverConsumedMediaTypes = $this->getRoute()->getConsumedMediaTypes();
131
132 13
        foreach ($serverConsumedMediaTypes as $serverConsumedMediaType) {
133
            if (
134 12
                strcasecmp(
135 12
                    $clientProducedMediaType->getIdentifier(),
136 12
                    $serverConsumedMediaType->getIdentifier(),
137 12
                ) === 0
138
            ) {
139 10
                return true;
140
            }
141
        }
142
143 3
        return false;
144
    }
145
146
    /**
147
     * @return Generator<int, LocaleInterface>
148
     *
149
     * @throws LogicException
150
     */
151 23
    public function getClientConsumedLocales(): Generator
152
    {
153
        // @codeCoverageIgnoreStart
154
        if (!extension_loaded('intl')) {
155
            throw new LogicException(
156
                'To get the locales consumed by the client, ' .
157
                'the Intl (https://www.php.net/intl) extension must be installed.'
158
            );
159
        }
160
        // @codeCoverageIgnoreEnd
161
162 23
        $values = HeaderParser::parseHeader($this->request->getHeaderLine(HeaderName::ACCEPT_LANGUAGE));
163 23
        if ($values === []) {
164 2
            return;
165
        }
166
167
        // https://github.com/php/php-src/blob/d9549d2ee215cb04aa5d2e3195c608d581fb272c/ext/standard/array.c#L900-L903
168 21
        usort($values, static fn(array $a, array $b): int => (
169 16
            (float) ($b[1]['q'] ?? '1') <=> (float) ($a[1]['q'] ?? '1')
170 21
        ));
171
172 21
        foreach ($values as [$identifier]) {
173
            /** @var array{language?: string, region?: string}|null $subtags */
174 21
            $subtags = \Locale::parseLocale($identifier);
175
176 21
            if (isset($subtags['language'])) {
177 20
                yield new Locale($subtags['language'], $subtags['region'] ?? null);
178
            }
179
        }
180
    }
181
182
    /**
183
     * @param T ...$serverProducedLanguages
184
     *
185
     * @return T|null
186
     *
187
     * @template T of LanguageInterface
188
     */
189 20
    public function getClientPreferredLanguage(LanguageInterface ...$serverProducedLanguages): ?LanguageInterface
190
    {
191 20
        if ($serverProducedLanguages === []) {
192 7
            return null;
193
        }
194
195 13
        foreach ($this->getClientConsumedLocales() as $clientConsumedLocale) {
196 12
            foreach ($serverProducedLanguages as $serverProducedLanguage) {
197 12
                if ($clientConsumedLocale->getLanguageCode() === $serverProducedLanguage->getCode()) {
198 11
                    return $serverProducedLanguage;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $serverProducedLanguage returns the type Sunrise\Http\Router\LanguageInterface which is incompatible with the documented return type Sunrise\Http\Router\T|null.
Loading history...
199
                }
200
            }
201
        }
202
203 2
        return null;
204
    }
205
206
    /**
207
     * @inheritDoc
208
     */
209 1
    public function getProtocolVersion(): string
210
    {
211 1
        return $this->request->getProtocolVersion();
212
    }
213
214
    /**
215
     * @inheritDoc
216
     */
217 1
    public function withProtocolVersion($version): self
218
    {
219 1
        $clone = clone $this;
220 1
        $clone->request = $this->request->withProtocolVersion($version);
221
222 1
        return $clone;
223
    }
224
225
    /**
226
     * @inheritDoc
227
     */
228 1
    public function getHeaders(): array
229
    {
230 1
        return $this->request->getHeaders();
231
    }
232
233
    /**
234
     * @inheritDoc
235
     */
236 2
    public function hasHeader($name): bool
237
    {
238 2
        return $this->request->hasHeader($name);
239
    }
240
241
    /**
242
     * @inheritDoc
243
     */
244 1
    public function getHeader($name): array
245
    {
246 1
        return $this->request->getHeader($name);
247
    }
248
249
    /**
250
     * @inheritDoc
251
     */
252 1
    public function getHeaderLine($name): string
253
    {
254 1
        return $this->request->getHeaderLine($name);
255
    }
256
257
    /**
258
     * @inheritDoc
259
     */
260 1
    public function withHeader($name, $value): self
261
    {
262 1
        $clone = clone $this;
263 1
        $clone->request = $this->request->withHeader($name, $value);
264
265 1
        return $clone;
266
    }
267
268
    /**
269
     * @inheritDoc
270
     */
271 1
    public function withAddedHeader($name, $value): self
272
    {
273 1
        $clone = clone $this;
274 1
        $clone->request = $this->request->withAddedHeader($name, $value);
275
276 1
        return $clone;
277
    }
278
279
    /**
280
     * @inheritDoc
281
     */
282 1
    public function withoutHeader($name): self
283
    {
284 1
        $clone = clone $this;
285 1
        $clone->request = $this->request->withoutHeader($name);
286
287 1
        return $clone;
288
    }
289
290
    /**
291
     * @inheritDoc
292
     */
293 1
    public function getBody(): StreamInterface
294
    {
295 1
        return $this->request->getBody();
296
    }
297
298
    /**
299
     * @inheritDoc
300
     */
301 1
    public function withBody(StreamInterface $body): self
302
    {
303 1
        $clone = clone $this;
304 1
        $clone->request = $this->request->withBody($body);
305
306 1
        return $clone;
307
    }
308
309
    /**
310
     * @inheritDoc
311
     */
312 1
    public function getMethod(): string
313
    {
314 1
        return $this->request->getMethod();
315
    }
316
317
    /**
318
     * @inheritDoc
319
     */
320 1
    public function withMethod($method): self
321
    {
322 1
        $clone = clone $this;
323 1
        $clone->request = $this->request->withMethod($method);
324
325 1
        return $clone;
326
    }
327
328
    /**
329
     * @inheritDoc
330
     */
331 1
    public function getUri(): UriInterface
332
    {
333 1
        return $this->request->getUri();
334
    }
335
336
    /**
337
     * @inheritDoc
338
     */
339 1
    public function withUri(UriInterface $uri, $preserveHost = false): self
340
    {
341 1
        $clone = clone $this;
342 1
        $clone->request = $this->request->withUri($uri, $preserveHost);
343
344 1
        return $clone;
345
    }
346
347
    /**
348
     * @inheritDoc
349
     */
350 1
    public function getRequestTarget(): string
351
    {
352 1
        return $this->request->getRequestTarget();
353
    }
354
355
    /**
356
     * @inheritDoc
357
     */
358 1
    public function withRequestTarget($requestTarget): self
359
    {
360 1
        $clone = clone $this;
361 1
        $clone->request = $this->request->withRequestTarget($requestTarget);
362
363 1
        return $clone;
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     *
369
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
370
     */
371 1
    public function getServerParams(): array
372
    {
373 1
        return $this->request->getServerParams();
374
    }
375
376
    /**
377
     * {@inheritDoc}
378
     *
379
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
380
     */
381 1
    public function getQueryParams(): array
382
    {
383 1
        return $this->request->getQueryParams();
384
    }
385
386
    /**
387
     * {@inheritDoc}
388
     *
389
     * @param array<array-key, mixed> $query
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
390
     *
391
     * @return static
392
     */
393 1
    public function withQueryParams(array $query): self
394
    {
395 1
        $clone = clone $this;
396 1
        $clone->request = $this->request->withQueryParams($query);
397
398 1
        return $clone;
399
    }
400
401
    /**
402
     * {@inheritDoc}
403
     *
404
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
405
     */
406 15
    public function getCookieParams(): array
407
    {
408 15
        return $this->request->getCookieParams();
409
    }
410
411
    /**
412
     * {@inheritDoc}
413
     *
414
     * @param array<array-key, mixed> $cookies
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
415
     *
416
     * @return static
417
     */
418 1
    public function withCookieParams(array $cookies): self
419
    {
420 1
        $clone = clone $this;
421 1
        $clone->request = $this->request->withCookieParams($cookies);
422
423 1
        return $clone;
424
    }
425
426
    /**
427
     * {@inheritDoc}
428
     *
429
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
430
     */
431 1
    public function getUploadedFiles(): array
432
    {
433 1
        return $this->request->getUploadedFiles();
434
    }
435
436
    /**
437
     * {@inheritDoc}
438
     *
439
     * @param array<array-key, mixed> $uploadedFiles
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
440
     *
441
     * @return static
442
     */
443 1
    public function withUploadedFiles(array $uploadedFiles): self
444
    {
445 1
        $clone = clone $this;
446 1
        $clone->request = $this->request->withUploadedFiles($uploadedFiles);
447
448 1
        return $clone;
449
    }
450
451
    /**
452
     * {@inheritDoc}
453
     *
454
     * @return array<array-key, mixed>|object|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed>|object|null at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>|object|null.
Loading history...
455
     */
456 1
    public function getParsedBody(): mixed
457
    {
458 1
        return $this->request->getParsedBody();
459
    }
460
461
    /**
462
     * {@inheritDoc}
463
     *
464
     * @param array<array-key, mixed>|object|null $data
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed>|object|null at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>|object|null.
Loading history...
465
     *
466
     * @return static
467
     */
468 1
    public function withParsedBody($data): self
469
    {
470 1
        $clone = clone $this;
471 1
        $clone->request = $this->request->withParsedBody($data);
472
473 1
        return $clone;
474
    }
475
476
    /**
477
     * {@inheritDoc}
478
     *
479
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
480
     */
481 1
    public function getAttributes(): array
482
    {
483 1
        return $this->request->getAttributes();
484
    }
485
486
    /**
487
     * {@inheritDoc}
488
     *
489
     * @param string $name
490
     * @param mixed $default
491
     *
492
     * @return mixed
493
     */
494 1
    public function getAttribute($name, $default = null): mixed
495
    {
496 1
        return $this->request->getAttribute($name, $default);
497
    }
498
499
    /**
500
     * {@inheritDoc}
501
     *
502
     * @param string $name
503
     * @param mixed $value
504
     *
505
     * @return static
506
     */
507 1
    public function withAttribute($name, $value): self
508
    {
509 1
        $clone = clone $this;
510 1
        $clone->request = $this->request->withAttribute($name, $value);
511
512 1
        return $clone;
513
    }
514
515
    /**
516
     * {@inheritDoc}
517
     *
518
     * @param string $name
519
     *
520
     * @return static
521
     */
522 1
    public function withoutAttribute($name): self
523
    {
524 1
        $clone = clone $this;
525 1
        $clone->request = $this->request->withoutAttribute($name);
526
527 1
        return $clone;
528
    }
529
}
530