• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

slimphp / Slim-Http / 19014509820

02 Nov 2025 03:42PM UTC coverage: 99.672% (-0.3%) from 100.0%
19014509820

Pull #247

github

web-flow
Merge 4441831b1 into fd26ab618
Pull Request #247: Update to PHPStan 2.x

5 of 6 new or added lines in 2 files covered. (83.33%)

1 existing line in 1 file now uncovered.

304 of 305 relevant lines covered (99.67%)

18.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.42
/src/ServerRequest.php
1
<?php
2

3
/**
4
 * Slim Framework (https://slimframework.com)
5
 *
6
 * @license https://github.com/slimphp/Slim-Http/blob/master/LICENSE.md (MIT License)
7
 */
8

9
declare(strict_types=1);
10

11
namespace Slim\Http;
12

13
use Closure;
14
use Psr\Http\Message\MessageInterface;
15
use Psr\Http\Message\RequestInterface;
16
use Psr\Http\Message\ServerRequestInterface;
17
use Psr\Http\Message\StreamInterface;
18
use Psr\Http\Message\UriInterface;
19
use RuntimeException;
20

21
use function array_merge;
22
use function count;
23
use function explode;
24
use function is_array;
25
use function is_null;
26
use function is_object;
27
use function json_decode;
28
use function libxml_clear_errors;
29
use function libxml_disable_entity_loader;
30
use function libxml_use_internal_errors;
31
use function parse_str;
32
use function preg_split;
33
use function property_exists;
34
use function simplexml_load_string;
35
use function strtolower;
36

37
use const LIBXML_VERSION;
38

39
class ServerRequest implements ServerRequestInterface
40
{
41
    protected ServerRequestInterface $serverRequest;
42

43
    /** @var array<string, callable|null> */
44
    protected array $bodyParsers;
45

46
    /**
47
     * @param ServerRequestInterface $serverRequest
48
     */
49
    final public function __construct(ServerRequestInterface $serverRequest)
50
    {
51
        $this->serverRequest = $serverRequest;
183✔
52

53
        $this->registerMediaTypeParser('application/json', function (string $input) {
183✔
54
            $result = json_decode($input, true);
4✔
55

56
            if (!is_array($result)) {
4✔
57
                return null;
2✔
58
            }
59

60
            return $result;
2✔
61
        });
183✔
62

63
        $xmlParserCallable = function (string $input) {
183✔
64
            $backup = self::disableXmlEntityLoader(true);
5✔
65
            $backup_errors = libxml_use_internal_errors(true);
5✔
66
            $result = simplexml_load_string($input);
5✔
67

68
            self::disableXmlEntityLoader($backup);
5✔
69
            libxml_clear_errors();
5✔
70
            libxml_use_internal_errors($backup_errors);
5✔
71

72
            if ($result === false) {
5✔
73
                return null;
2✔
74
            }
75

76
            return $result;
3✔
77
        };
183✔
78

79
        $this->registerMediaTypeParser('application/xml', $xmlParserCallable);
183✔
80
        $this->registerMediaTypeParser('text/xml', $xmlParserCallable);
183✔
81

82
        $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) {
183✔
83
            if (!is_string($input)) {
4✔
NEW
UNCOV
84
                return null;
×
85
            }
86
            parse_str($input, $data);
4✔
87
            return $data;
4✔
88
        });
183✔
89
    }
90

91
    /**
92
     * Disable magic setter to ensure immutability
93
     * @param mixed $name
94
     * @param mixed $value
95
     * @return void
96
     */
97
    public function __set($name, $value)
98
    {
99
    }
1✔
100

101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function getAttribute($name, $default = null)
105
    {
106
        return $this->serverRequest->getAttribute($name, $default);
6✔
107
    }
108

109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function getAttributes(): array
113
    {
114
        return $this->serverRequest->getAttributes();
5✔
115
    }
116

117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function getBody(): StreamInterface
121
    {
122
        return $this->serverRequest->getBody();
17✔
123
    }
124

125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function getCookieParams(): array
129
    {
130
        return $this->serverRequest->getCookieParams();
6✔
131
    }
132

133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function getHeader($name): array
137
    {
138
        return $this->serverRequest->getHeader($name);
5✔
139
    }
140

141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function getHeaderLine($name): string
145
    {
146
        return $this->serverRequest->getHeaderLine($name);
17✔
147
    }
148

149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function getHeaders(): array
153
    {
154
        return $this->serverRequest->getHeaders();
5✔
155
    }
156

157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function getMethod(): string
161
    {
162
        return $this->serverRequest->getMethod();
10✔
163
    }
164

165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function getParsedBody()
169
    {
170
        $parsedBody = $this->serverRequest->getParsedBody();
38✔
171

172
        if (!empty($parsedBody)) {
38✔
173
            return $parsedBody;
7✔
174
        }
175

176
        $mediaType = $this->getMediaType();
35✔
177
        if ($mediaType === null) {
35✔
178
            return $parsedBody;
19✔
179
        }
180

181
        // Check if this specific media type has a parser registered first
182
        if (!isset($this->bodyParsers[$mediaType])) {
16✔
183
            // If not, look for a media type with a structured syntax suffix (RFC 6839)
184
            $parts = explode('+', $mediaType);
3✔
185
            if (count($parts) >= 2) {
3✔
186
                $mediaType = 'application/' . $parts[count($parts) - 1];
3✔
187
            }
188
        }
189

190
        if (isset($this->bodyParsers[$mediaType])) {
16✔
191
            $body = (string)$this->getBody();
15✔
192
            $parsed = $this->bodyParsers[$mediaType]($body);
15✔
193

194
            if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) {
15✔
195
                throw new RuntimeException(
1✔
196
                    'Request body media type parser return value must be an array, an object, or null'
1✔
197
                );
1✔
198
            }
199

200
            return $parsed;
14✔
201
        }
202

203
        return null;
1✔
204
    }
205

206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function getProtocolVersion(): string
210
    {
211
        return $this->serverRequest->getProtocolVersion();
4✔
212
    }
213

214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function getQueryParams(): array
218
    {
219
        $queryParams = $this->serverRequest->getQueryParams();
12✔
220

221
        if (!empty($queryParams)) {
12✔
222
            return $queryParams;
11✔
223
        }
224

225
        $parsedQueryParams = [];
10✔
226
        parse_str($this->serverRequest->getUri()->getQuery(), $parsedQueryParams);
10✔
227

228
        return $parsedQueryParams;
10✔
229
    }
230

231
    /**
232
     * {@inheritdoc}
233
     */
234
    public function getRequestTarget(): string
235
    {
236
        return $this->serverRequest->getRequestTarget();
6✔
237
    }
238

239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function getServerParams(): array
243
    {
244
        return $this->serverRequest->getServerParams();
3✔
245
    }
246

247
    /**
248
     * {@inheritdoc}
249
     */
250
    public function getUploadedFiles(): array
251
    {
252
        return $this->serverRequest->getUploadedFiles();
3✔
253
    }
254

255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function getUri(): UriInterface
259
    {
260
        return $this->serverRequest->getUri();
4✔
261
    }
262

263
    /**
264
     * {@inheritdoc}
265
     */
266
    public function hasHeader($name): bool
267
    {
268
        return $this->serverRequest->hasHeader($name);
6✔
269
    }
270

271
    /**
272
     * {@inheritdoc}
273
     */
274
    public function withAddedHeader($name, $value): MessageInterface
275
    {
276
        $serverRequest = $this->serverRequest->withAddedHeader($name, $value);
31✔
277
        return new static($serverRequest);
17✔
278
    }
279

280
    /**
281
     * {@inheritdoc}
282
     */
283
    public function withAttribute($name, $value): ServerRequestInterface
284
    {
285
        $serverRequest = $this->serverRequest->withAttribute($name, $value);
9✔
286
        return new static($serverRequest);
9✔
287
    }
288

289
    /**
290
     * Create a new instance with the specified derived request attributes.
291
     *
292
     * Note: This method is not part of the PSR-7 standard.
293
     *
294
     * This method allows setting all new derived request attributes as
295
     * described in getAttributes().
296
     *
297
     * This method MUST be implemented in such a way as to retain the
298
     * immutability of the message, and MUST return a new instance that has the
299
     * updated attributes.
300
     *
301
     * @param  array $attributes New attributes
302
     * @return static
303
     */
304
    public function withAttributes(array $attributes): ServerRequestInterface
305
    {
306
        $serverRequest = $this->serverRequest;
2✔
307

308
        foreach ($attributes as $attribute => $value) {
2✔
309
            $serverRequest = $serverRequest->withAttribute($attribute, $value);
2✔
310
        }
311

312
        return new static($serverRequest);
2✔
313
    }
314

315
    /**
316
     * {@inheritdoc}
317
     */
318
    public function withoutAttribute($name): ServerRequestInterface
319
    {
320
        $serverRequest = $this->serverRequest->withoutAttribute($name);
3✔
321
        return new static($serverRequest);
3✔
322
    }
323

324
    /**
325
     * {@inheritdoc}
326
     */
327
    public function withBody(StreamInterface $body): MessageInterface
328
    {
329
        $serverRequest = $this->serverRequest->withBody($body);
17✔
330
        return new static($serverRequest);
17✔
331
    }
332

333
    /**
334
     * {@inheritdoc}
335
     */
336
    public function withCookieParams(array $cookies): ServerRequestInterface
337
    {
338
        $serverRequest = $this->serverRequest->withCookieParams($cookies);
5✔
339
        return new static($serverRequest);
5✔
340
    }
341

342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function withHeader($name, $value): MessageInterface
346
    {
347
        $serverRequest = $this->serverRequest->withHeader($name, $value);
45✔
348
        return new static($serverRequest);
31✔
349
    }
350

351
    /**
352
     * {@inheritdoc}
353
     */
354
    public function withoutHeader($name): MessageInterface
355
    {
356
        $serverRequest = $this->serverRequest->withoutHeader($name);
3✔
357
        return new static($serverRequest);
3✔
358
    }
359

360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function withMethod($method): RequestInterface
364
    {
365
        $serverRequest = $this->serverRequest->withMethod($method);
18✔
366
        return new static($serverRequest);
10✔
367
    }
368

369
    /**
370
     * {@inheritdoc}
371
     */
372
    public function withParsedBody($data): ServerRequestInterface
373
    {
374
        $serverRequest = $this->serverRequest->withParsedBody($data);
21✔
375
        return new static($serverRequest);
13✔
376
    }
377

378
    /**
379
     * {@inheritdoc}
380
     */
381
    public function withProtocolVersion($version): MessageInterface
382
    {
383
        $serverRequest = $this->serverRequest->withProtocolVersion($version);
4✔
384
        return new static($serverRequest);
4✔
385
    }
386

387
    /**
388
     * {@inheritdoc}
389
     */
390
    public function withQueryParams(array $query): ServerRequestInterface
391
    {
392
        $serverRequest = $this->serverRequest->withQueryParams($query);
5✔
393
        return new static($serverRequest);
5✔
394
    }
395

396
    /**
397
     * {@inheritdoc}
398
     */
399
    public function withRequestTarget($requestTarget): RequestInterface
400
    {
401
        $serverRequest = $this->serverRequest->withRequestTarget($requestTarget);
3✔
402
        return new static($serverRequest);
3✔
403
    }
404

405
    /**
406
     * {@inheritdoc}
407
     */
408
    public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
409
    {
410
        $serverRequest = $this->serverRequest->withUploadedFiles($uploadedFiles);
3✔
411
        return new static($serverRequest);
3✔
412
    }
413

414
    /**
415
     * {@inheritdoc}
416
     */
417
    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
418
    {
419
        $serverRequest = $this->serverRequest->withUri($uri, $preserveHost);
9✔
420
        return new static($serverRequest);
9✔
421
    }
422

423
    /**
424
     * Get serverRequest content character set, if known.
425
     *
426
     * Note: This method is not part of the PSR-7 standard.
427
     *
428
     * @return string|null
429
     */
430
    public function getContentCharset(): ?string
431
    {
432
        $mediaTypeParams = $this->getMediaTypeParams();
3✔
433

434
        if (isset($mediaTypeParams['charset'])) {
3✔
435
            return $mediaTypeParams['charset'];
1✔
436
        }
437

438
        return null;
2✔
439
    }
440

441
    /**
442
     * Get serverRequest content type.
443
     *
444
     * Note: This method is not part of the PSR-7 standard.
445
     *
446
     * @return string|null The serverRequest content type, if known
447
     */
448
    public function getContentType(): ?string
449
    {
450
        $result = $this->serverRequest->getHeader('Content-Type');
46✔
451
        return $result ? $result[0] : null;
46✔
452
    }
453

454
    /**
455
     * Get serverRequest content length, if known.
456
     *
457
     * Note: This method is not part of the PSR-7 standard.
458
     *
459
     * @return int|null
460
     */
461
    public function getContentLength(): ?int
462
    {
463
        $result = $this->serverRequest->getHeader('Content-Length');
2✔
464
        return $result ? (int) $result[0] : null;
2✔
465
    }
466

467
    /**
468
     * Fetch cookie value from cookies sent by the client to the server.
469
     *
470
     * Note: This method is not part of the PSR-7 standard.
471
     *
472
     * @param string $key     The attribute name.
473
     * @param mixed  $default Default value to return if the attribute does not exist.
474
     *
475
     * @return mixed
476
     */
477
    public function getCookieParam(string $key, $default = null)
478
    {
479
        $cookies = $this->serverRequest->getCookieParams();
2✔
480
        $result = $default;
2✔
481

482
        if (isset($cookies[$key])) {
2✔
483
            $result = $cookies[$key];
1✔
484
        }
485

486
        return $result;
2✔
487
    }
488

489
    /**
490
     * Get serverRequest media type, if known.
491
     *
492
     * Note: This method is not part of the PSR-7 standard.
493
     *
494
     * @return string|null The serverRequest media type, minus content-type params
495
     */
496
    public function getMediaType(): ?string
497
    {
498
        $contentType = $this->getContentType();
38✔
499

500
        if ($contentType) {
38✔
501
            $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
18✔
502
            if ($contentTypeParts === false) {
18✔
503
                return null;
1✔
504
            }
505
            return strtolower($contentTypeParts[0]);
17✔
506
        }
507

508
        return null;
20✔
509
    }
510

511
    /**
512
     * Get serverRequest media type params, if known.
513
     *
514
     * Note: This method is not part of the PSR-7 standard.
515
     *
516
     * @return string[]
517
     */
518
    public function getMediaTypeParams(): array
519
    {
520
        $contentType = $this->getContentType();
6✔
521
        $contentTypeParams = [];
6✔
522

523
        if ($contentType) {
6✔
524
            $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
4✔
525
            if ($contentTypeParts !== false) {
4✔
526
                $contentTypePartsLength = count($contentTypeParts);
4✔
527
                for ($i = 1; $i < $contentTypePartsLength; $i++) {
4✔
528
                    $paramParts = explode('=', $contentTypeParts[$i]);
2✔
529
                    /** @var string[] $paramParts */
530
                    $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
2✔
531
                }
532
            }
533
        }
534

535
        return $contentTypeParams;
6✔
536
    }
537

538
    /**
539
     * Fetch serverRequest parameter value from body or query string (in that order).
540
     *
541
     * Note: This method is not part of the PSR-7 standard.
542
     *
543
     * @param  string $key The parameter key.
544
     * @param  mixed  $default The default value.
545
     *
546
     * @return mixed The parameter value.
547
     */
548
    public function getParam(string $key, $default = null)
549
    {
550
        $postParams = $this->getParsedBody();
4✔
551
        $getParams = $this->getQueryParams();
4✔
552
        $result = $default;
4✔
553

554
        if (is_array($postParams) && isset($postParams[$key])) {
4✔
555
            $result = $postParams[$key];
2✔
556
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
3✔
557
            $result = $postParams->$key;
1✔
558
        } elseif (isset($getParams[$key])) {
2✔
559
            $result = $getParams[$key];
1✔
560
        }
561

562
        return $result;
4✔
563
    }
564

565
    /**
566
     * Fetch associative array of body and query string parameters.
567
     *
568
     * Note: This method is not part of the PSR-7 standard.
569
     *
570
     * @return array
571
     */
572
    public function getParams(): array
573
    {
574
        $params = $this->getQueryParams();
2✔
575
        $postParams = $this->getParsedBody();
2✔
576

577
        if ($postParams) {
2✔
578
            $params = array_merge($params, (array)$postParams);
1✔
579
        }
580

581
        return $params;
2✔
582
    }
583

584
    /**
585
     * Fetch parameter value from serverRequest body.
586
     *
587
     * Note: This method is not part of the PSR-7 standard.
588
     *
589
     * @param string $key
590
     * @param mixed $default
591
     *
592
     * @return mixed
593
     */
594
    public function getParsedBodyParam(string $key, $default = null)
595
    {
596
        $postParams = $this->getParsedBody();
1✔
597
        $result = $default;
1✔
598

599
        if (is_array($postParams) && isset($postParams[$key])) {
1✔
600
            $result = $postParams[$key];
1✔
601
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
1✔
602
            $result = $postParams->$key;
1✔
603
        }
604

605
        return $result;
1✔
606
    }
607

608
    /**
609
     * Fetch parameter value from query string.
610
     *
611
     * Note: This method is not part of the PSR-7 standard.
612
     *
613
     * @param string $key
614
     * @param mixed $default
615
     *
616
     * @return mixed
617
     */
618
    public function getQueryParam(string $key, $default = null)
619
    {
620
        $getParams = $this->getQueryParams();
1✔
621
        $result = $default;
1✔
622

623
        if (isset($getParams[$key])) {
1✔
624
            $result = $getParams[$key];
1✔
625
        }
626

627
        return $result;
1✔
628
    }
629

630
    /**
631
     * Retrieve a server parameter.
632
     *
633
     * Note: This method is not part of the PSR-7 standard.
634
     *
635
     * @param string $key
636
     * @param mixed  $default
637
     * @return mixed
638
     */
639
    public function getServerParam(string $key, $default = null)
640
    {
641
        $serverParams = $this->serverRequest->getServerParams();
2✔
642
        return $serverParams[$key] ?? $default;
2✔
643
    }
644

645
    /**
646
     * Register media type parser.
647
     *
648
     * Note: This method is not part of the PSR-7 standard.
649
     *
650
     * @param string   $mediaType A HTTP media type (excluding content-type params).
651
     * @param callable $callable  A callable that returns parsed contents for media type.
652
     * @return static
653
     */
654
    public function registerMediaTypeParser(string $mediaType, callable $callable): ServerRequestInterface
655
    {
656
        if ($callable instanceof Closure) {
183✔
657
            $callable = $callable->bindTo($this);
183✔
658
        }
659

660
        $this->bodyParsers[$mediaType] = $callable;
183✔
661

662
        return $this;
183✔
663
    }
664

665
    /**
666
     * Is this a DELETE serverRequest?
667
     *
668
     * Note: This method is not part of the PSR-7 standard.
669
     *
670
     * @return bool
671
     */
672
    public function isDelete(): bool
673
    {
674
        return $this->isMethod('DELETE');
1✔
675
    }
676

677
    /**
678
     * Is this a GET serverRequest?
679
     *
680
     * Note: This method is not part of the PSR-7 standard.
681
     *
682
     * @return bool
683
     */
684
    public function isGet(): bool
685
    {
686
        return $this->isMethod('GET');
1✔
687
    }
688

689
    /**
690
     * Is this a HEAD serverRequest?
691
     *
692
     * Note: This method is not part of the PSR-7 standard.
693
     *
694
     * @return bool
695
     */
696
    public function isHead(): bool
697
    {
698
        return $this->isMethod('HEAD');
1✔
699
    }
700

701
    /**
702
     * Does this serverRequest use a given method?
703
     *
704
     * Note: This method is not part of the PSR-7 standard.
705
     *
706
     * @param  string $method HTTP method
707
     * @return bool
708
     */
709
    public function isMethod(string $method): bool
710
    {
711
        return $this->serverRequest->getMethod() === $method;
7✔
712
    }
713

714
    /**
715
     * Is this a OPTIONS serverRequest?
716
     *
717
     * Note: This method is not part of the PSR-7 standard.
718
     *
719
     * @return bool
720
     */
721
    public function isOptions(): bool
722
    {
723
        return $this->isMethod('OPTIONS');
1✔
724
    }
725

726
    /**
727
     * Is this a PATCH serverRequest?
728
     *
729
     * Note: This method is not part of the PSR-7 standard.
730
     *
731
     * @return bool
732
     */
733
    public function isPatch(): bool
734
    {
735
        return $this->isMethod('PATCH');
1✔
736
    }
737

738
    /**
739
     * Is this a POST serverRequest?
740
     *
741
     * Note: This method is not part of the PSR-7 standard.
742
     *
743
     * @return bool
744
     */
745
    public function isPost(): bool
746
    {
747
        return $this->isMethod('POST');
1✔
748
    }
749

750
    /**
751
     * Is this a PUT serverRequest?
752
     *
753
     * Note: This method is not part of the PSR-7 standard.
754
     *
755
     * @return bool
756
     */
757
    public function isPut(): bool
758
    {
759
        return $this->isMethod('PUT');
1✔
760
    }
761

762
    /**
763
     * Is this an XHR serverRequest?
764
     *
765
     * Note: This method is not part of the PSR-7 standard.
766
     *
767
     * @return bool
768
     */
769
    public function isXhr(): bool
770
    {
771
        return $this->serverRequest->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
1✔
772
    }
773

774
    private static function disableXmlEntityLoader(bool $disable): bool
775
    {
776
        if (LIBXML_VERSION >= 20900) {
5✔
777
            // libxml >= 2.9.0 disables entity loading by default, so it is
778
            // safe to skip the real call (deprecated in PHP 8).
779
            return true;
5✔
780
        }
781

782
        // @codeCoverageIgnoreStart
783
        return libxml_disable_entity_loader($disable);
784
        // @codeCoverageIgnoreEnd
785
    }
786
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc