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

slimphp / Slim-Http / 19014395251

02 Nov 2025 03:32PM UTC coverage: 99.029% (-1.0%) from 100.0%
19014395251

Pull #247

github

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

5 of 8 new or added lines in 2 files covered. (62.5%)

3 existing lines in 1 file now uncovered.

306 of 309 relevant lines covered (99.03%)

18.2 hits per line

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

98.3
/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 ($input) {
183✔
54
            if (!is_string($input)) {
4✔
NEW
UNCOV
55
                return null;
×
56
            }
57
            $result = json_decode($input, true);
4✔
58

59
            if (!is_array($result)) {
4✔
60
                return null;
2✔
61
            }
62

63
            return $result;
2✔
64
        });
183✔
65

66
        $xmlParserCallable = function ($input) {
183✔
67
            if (!is_string($input)) {
5✔
NEW
UNCOV
68
                return null;
×
69
            }
70
            $backup = self::disableXmlEntityLoader(true);
5✔
71
            $backup_errors = libxml_use_internal_errors(true);
5✔
72
            $result = simplexml_load_string($input);
5✔
73

74
            self::disableXmlEntityLoader($backup);
5✔
75
            libxml_clear_errors();
5✔
76
            libxml_use_internal_errors($backup_errors);
5✔
77

78
            if ($result === false) {
5✔
79
                return null;
2✔
80
            }
81

82
            return $result;
3✔
83
        };
183✔
84

85
        $this->registerMediaTypeParser('application/xml', $xmlParserCallable);
183✔
86
        $this->registerMediaTypeParser('text/xml', $xmlParserCallable);
183✔
87

88
        $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) {
183✔
89
            if (!is_string($input)) {
4✔
NEW
UNCOV
90
                return null;
×
91
            }
92
            parse_str($input, $data);
4✔
93
            return $data;
4✔
94
        });
183✔
95
    }
96

97
    /**
98
     * Disable magic setter to ensure immutability
99
     * @param mixed $name
100
     * @param mixed $value
101
     * @return void
102
     */
103
    public function __set($name, $value)
104
    {
105
    }
1✔
106

107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function getAttribute($name, $default = null)
111
    {
112
        return $this->serverRequest->getAttribute($name, $default);
6✔
113
    }
114

115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function getAttributes(): array
119
    {
120
        return $this->serverRequest->getAttributes();
5✔
121
    }
122

123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function getBody(): StreamInterface
127
    {
128
        return $this->serverRequest->getBody();
17✔
129
    }
130

131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function getCookieParams(): array
135
    {
136
        return $this->serverRequest->getCookieParams();
6✔
137
    }
138

139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function getHeader($name): array
143
    {
144
        return $this->serverRequest->getHeader($name);
5✔
145
    }
146

147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function getHeaderLine($name): string
151
    {
152
        return $this->serverRequest->getHeaderLine($name);
17✔
153
    }
154

155
    /**
156
     * {@inheritdoc}
157
     */
158
    public function getHeaders(): array
159
    {
160
        return $this->serverRequest->getHeaders();
5✔
161
    }
162

163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function getMethod(): string
167
    {
168
        return $this->serverRequest->getMethod();
10✔
169
    }
170

171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function getParsedBody()
175
    {
176
        $parsedBody = $this->serverRequest->getParsedBody();
38✔
177

178
        if (!empty($parsedBody)) {
38✔
179
            return $parsedBody;
7✔
180
        }
181

182
        $mediaType = $this->getMediaType();
35✔
183
        if ($mediaType === null) {
35✔
184
            return $parsedBody;
19✔
185
        }
186

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

196
        if (isset($this->bodyParsers[$mediaType])) {
16✔
197
            $body = (string)$this->getBody();
15✔
198
            $parsed = $this->bodyParsers[$mediaType]($body);
15✔
199

200
            if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) {
15✔
201
                throw new RuntimeException(
1✔
202
                    'Request body media type parser return value must be an array, an object, or null'
1✔
203
                );
1✔
204
            }
205

206
            return $parsed;
14✔
207
        }
208

209
        return null;
1✔
210
    }
211

212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getProtocolVersion(): string
216
    {
217
        return $this->serverRequest->getProtocolVersion();
4✔
218
    }
219

220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function getQueryParams(): array
224
    {
225
        $queryParams = $this->serverRequest->getQueryParams();
12✔
226

227
        if (!empty($queryParams)) {
12✔
228
            return $queryParams;
11✔
229
        }
230

231
        $parsedQueryParams = [];
10✔
232
        parse_str($this->serverRequest->getUri()->getQuery(), $parsedQueryParams);
10✔
233

234
        return $parsedQueryParams;
10✔
235
    }
236

237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function getRequestTarget(): string
241
    {
242
        return $this->serverRequest->getRequestTarget();
6✔
243
    }
244

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

253
    /**
254
     * {@inheritdoc}
255
     */
256
    public function getUploadedFiles(): array
257
    {
258
        return $this->serverRequest->getUploadedFiles();
3✔
259
    }
260

261
    /**
262
     * {@inheritdoc}
263
     */
264
    public function getUri(): UriInterface
265
    {
266
        return $this->serverRequest->getUri();
4✔
267
    }
268

269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function hasHeader($name): bool
273
    {
274
        return $this->serverRequest->hasHeader($name);
6✔
275
    }
276

277
    /**
278
     * {@inheritdoc}
279
     */
280
    public function withAddedHeader($name, $value): MessageInterface
281
    {
282
        $serverRequest = $this->serverRequest->withAddedHeader($name, $value);
31✔
283
        return new static($serverRequest);
17✔
284
    }
285

286
    /**
287
     * {@inheritdoc}
288
     */
289
    public function withAttribute($name, $value): ServerRequestInterface
290
    {
291
        $serverRequest = $this->serverRequest->withAttribute($name, $value);
9✔
292
        return new static($serverRequest);
9✔
293
    }
294

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

314
        foreach ($attributes as $attribute => $value) {
2✔
315
            $serverRequest = $serverRequest->withAttribute($attribute, $value);
2✔
316
        }
317

318
        return new static($serverRequest);
2✔
319
    }
320

321
    /**
322
     * {@inheritdoc}
323
     */
324
    public function withoutAttribute($name): ServerRequestInterface
325
    {
326
        $serverRequest = $this->serverRequest->withoutAttribute($name);
3✔
327
        return new static($serverRequest);
3✔
328
    }
329

330
    /**
331
     * {@inheritdoc}
332
     */
333
    public function withBody(StreamInterface $body): MessageInterface
334
    {
335
        $serverRequest = $this->serverRequest->withBody($body);
17✔
336
        return new static($serverRequest);
17✔
337
    }
338

339
    /**
340
     * {@inheritdoc}
341
     */
342
    public function withCookieParams(array $cookies): ServerRequestInterface
343
    {
344
        $serverRequest = $this->serverRequest->withCookieParams($cookies);
5✔
345
        return new static($serverRequest);
5✔
346
    }
347

348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function withHeader($name, $value): MessageInterface
352
    {
353
        $serverRequest = $this->serverRequest->withHeader($name, $value);
45✔
354
        return new static($serverRequest);
31✔
355
    }
356

357
    /**
358
     * {@inheritdoc}
359
     */
360
    public function withoutHeader($name): MessageInterface
361
    {
362
        $serverRequest = $this->serverRequest->withoutHeader($name);
3✔
363
        return new static($serverRequest);
3✔
364
    }
365

366
    /**
367
     * {@inheritdoc}
368
     */
369
    public function withMethod($method): RequestInterface
370
    {
371
        $serverRequest = $this->serverRequest->withMethod($method);
18✔
372
        return new static($serverRequest);
10✔
373
    }
374

375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function withParsedBody($data): ServerRequestInterface
379
    {
380
        $serverRequest = $this->serverRequest->withParsedBody($data);
21✔
381
        return new static($serverRequest);
13✔
382
    }
383

384
    /**
385
     * {@inheritdoc}
386
     */
387
    public function withProtocolVersion($version): MessageInterface
388
    {
389
        $serverRequest = $this->serverRequest->withProtocolVersion($version);
4✔
390
        return new static($serverRequest);
4✔
391
    }
392

393
    /**
394
     * {@inheritdoc}
395
     */
396
    public function withQueryParams(array $query): ServerRequestInterface
397
    {
398
        $serverRequest = $this->serverRequest->withQueryParams($query);
5✔
399
        return new static($serverRequest);
5✔
400
    }
401

402
    /**
403
     * {@inheritdoc}
404
     */
405
    public function withRequestTarget($requestTarget): RequestInterface
406
    {
407
        $serverRequest = $this->serverRequest->withRequestTarget($requestTarget);
3✔
408
        return new static($serverRequest);
3✔
409
    }
410

411
    /**
412
     * {@inheritdoc}
413
     */
414
    public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
415
    {
416
        $serverRequest = $this->serverRequest->withUploadedFiles($uploadedFiles);
3✔
417
        return new static($serverRequest);
3✔
418
    }
419

420
    /**
421
     * {@inheritdoc}
422
     */
423
    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
424
    {
425
        $serverRequest = $this->serverRequest->withUri($uri, $preserveHost);
9✔
426
        return new static($serverRequest);
9✔
427
    }
428

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

440
        if (isset($mediaTypeParams['charset'])) {
3✔
441
            return $mediaTypeParams['charset'];
1✔
442
        }
443

444
        return null;
2✔
445
    }
446

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

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

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

488
        if (isset($cookies[$key])) {
2✔
489
            $result = $cookies[$key];
1✔
490
        }
491

492
        return $result;
2✔
493
    }
494

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

506
        if ($contentType) {
38✔
507
            $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
18✔
508
            if ($contentTypeParts === false) {
18✔
509
                return null;
1✔
510
            }
511
            return strtolower($contentTypeParts[0]);
17✔
512
        }
513

514
        return null;
20✔
515
    }
516

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

529
        if ($contentType) {
6✔
530
            $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
4✔
531
            if ($contentTypeParts !== false) {
4✔
532
                $contentTypePartsLength = count($contentTypeParts);
4✔
533
                for ($i = 1; $i < $contentTypePartsLength; $i++) {
4✔
534
                    $paramParts = explode('=', $contentTypeParts[$i]);
2✔
535
                    /** @var string[] $paramParts */
536
                    $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
2✔
537
                }
538
            }
539
        }
540

541
        return $contentTypeParams;
6✔
542
    }
543

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

560
        if (is_array($postParams) && isset($postParams[$key])) {
4✔
561
            $result = $postParams[$key];
2✔
562
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
3✔
563
            $result = $postParams->$key;
1✔
564
        } elseif (isset($getParams[$key])) {
2✔
565
            $result = $getParams[$key];
1✔
566
        }
567

568
        return $result;
4✔
569
    }
570

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

583
        if ($postParams) {
2✔
584
            $params = array_merge($params, (array)$postParams);
1✔
585
        }
586

587
        return $params;
2✔
588
    }
589

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

605
        if (is_array($postParams) && isset($postParams[$key])) {
1✔
606
            $result = $postParams[$key];
1✔
607
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
1✔
608
            $result = $postParams->$key;
1✔
609
        }
610

611
        return $result;
1✔
612
    }
613

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

629
        if (isset($getParams[$key])) {
1✔
630
            $result = $getParams[$key];
1✔
631
        }
632

633
        return $result;
1✔
634
    }
635

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

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

666
        $this->bodyParsers[$mediaType] = $callable;
183✔
667

668
        return $this;
183✔
669
    }
670

671
    /**
672
     * Is this a DELETE serverRequest?
673
     *
674
     * Note: This method is not part of the PSR-7 standard.
675
     *
676
     * @return bool
677
     */
678
    public function isDelete(): bool
679
    {
680
        return $this->isMethod('DELETE');
1✔
681
    }
682

683
    /**
684
     * Is this a GET serverRequest?
685
     *
686
     * Note: This method is not part of the PSR-7 standard.
687
     *
688
     * @return bool
689
     */
690
    public function isGet(): bool
691
    {
692
        return $this->isMethod('GET');
1✔
693
    }
694

695
    /**
696
     * Is this a HEAD serverRequest?
697
     *
698
     * Note: This method is not part of the PSR-7 standard.
699
     *
700
     * @return bool
701
     */
702
    public function isHead(): bool
703
    {
704
        return $this->isMethod('HEAD');
1✔
705
    }
706

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

720
    /**
721
     * Is this a OPTIONS serverRequest?
722
     *
723
     * Note: This method is not part of the PSR-7 standard.
724
     *
725
     * @return bool
726
     */
727
    public function isOptions(): bool
728
    {
729
        return $this->isMethod('OPTIONS');
1✔
730
    }
731

732
    /**
733
     * Is this a PATCH serverRequest?
734
     *
735
     * Note: This method is not part of the PSR-7 standard.
736
     *
737
     * @return bool
738
     */
739
    public function isPatch(): bool
740
    {
741
        return $this->isMethod('PATCH');
1✔
742
    }
743

744
    /**
745
     * Is this a POST serverRequest?
746
     *
747
     * Note: This method is not part of the PSR-7 standard.
748
     *
749
     * @return bool
750
     */
751
    public function isPost(): bool
752
    {
753
        return $this->isMethod('POST');
1✔
754
    }
755

756
    /**
757
     * Is this a PUT serverRequest?
758
     *
759
     * Note: This method is not part of the PSR-7 standard.
760
     *
761
     * @return bool
762
     */
763
    public function isPut(): bool
764
    {
765
        return $this->isMethod('PUT');
1✔
766
    }
767

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

780
    private static function disableXmlEntityLoader(bool $disable): bool
781
    {
782
        if (LIBXML_VERSION >= 20900) {
5✔
783
            // libxml >= 2.9.0 disables entity loading by default, so it is
784
            // safe to skip the real call (deprecated in PHP 8).
785
            return true;
5✔
786
        }
787

788
        // @codeCoverageIgnoreStart
789
        return libxml_disable_entity_loader($disable);
790
        // @codeCoverageIgnoreEnd
791
    }
792
}
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