GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 42d282...d7c54a )
by Patrique
01:00
created

ServerRequest::withParsedBody()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 9.2248
c 0
b 0
f 0
cc 5
nc 3
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Patoui\Router;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Message\StreamInterface;
10
use Psr\Http\Message\UploadedFileInterface;
11
use Psr\Http\Message\UriInterface;
12
13
class ServerRequest implements ServerRequestInterface
14
{
15
    /**
16
     * @var string Represent the HTTP version number (e.g., "1.1", "1.0")
17
     */
18
    private $version;
19
20
    /**
21
     * @var array Contains header by key and array.
22
     * e.g. ['content-type' => ['application/json']]
23
     */
24
    private $headers;
25
26
    /** @var StreamInterface */
27
    private $body;
28
29
    /** @var string */
30
    private $requestTarget;
31
32
    /** @var string */
33
    private $method;
34
35
    /** @var UriInterface */
36
    private $uri;
37
38
    /** @var array */
39
    private $serverParams;
40
41
    /** @var array */
42
    private $cookieParams;
43
44
    /** @var array */
45
    private $queryParams;
46
47
    /** @var array */
48
    private $uploadedFiles;
49
50
    /** @var null|array|object */
51
    private $parsedBody;
52
53
    /** @var array */
54
    private $attributes;
55
56
    public function __construct(
57
        string $version,
58
        array $headers,
59
        StreamInterface $body,
60
        string $requestTarget,
61
        string $method,
62
        UriInterface $uri,
63
        array $serverParams,
64
        array $cookieParams,
65
        array $queryParams,
66
        array $uploadedFiles
67
    ) {
68
        $this->validateProtocolVersion($version);
69
        $this->validateHeaders($headers);
70
        $this->validateMethod($method);
71
72
        $this->version = $version;
73
        $this->headers = $headers;
74
        $this->body = $body;
75
        $this->requestTarget = $requestTarget;
76
        $this->method = $method;
77
        $this->uri = $uri;
78
        $this->serverParams = $serverParams;
79
        $this->cookieParams = $cookieParams;
80
        $this->queryParams = $queryParams;
81
        $this->uploadedFiles = $uploadedFiles;
82
        $this->attributes = [];
83
    }
84
85
    /**
86
     * Create instance of server request based on global values
87
     * @return static
88
     */
89
    public static function makeWithGlobals(): self
90
    {
91
        $headers = Headers::getHeadersArrayFromGlobals();
92
        $protocolVersion = str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL'] ?? '1.1');
93
        $requestTarget = $_SERVER['REQUEST_URI'] ?? '/';
94
        $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
95
96
        // TODO: identify potential risk of using globals
97
        return new static (
98
            $protocolVersion,
99
            $headers,
100
            new Stream('body'),
101
            $requestTarget,
102
            $method,
103
            new Uri($requestTarget),
104
            $_SERVER,
105
            $_COOKIE,
106
            $_GET,
107
            $_FILES
108
        );
109
    }
110
111
    /**
112
     * Retrieves the HTTP protocol version as a string.
113
     *
114
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
115
     *
116
     * @return string HTTP protocol version.
117
     */
118
    public function getProtocolVersion()
119
    {
120
        return $this->version;
121
    }
122
123
    /**
124
     * Return an instance with the specified HTTP protocol version.
125
     *
126
     * The version string MUST contain only the HTTP version number (e.g.,
127
     * "1.1", "1.0").
128
     *
129
     * This method MUST be implemented in such a way as to retain the
130
     * immutability of the message, and MUST return an instance that has the
131
     * new protocol version.
132
     *
133
     * @param  string  $version  HTTP protocol version
134
     * @return static
135
     */
136
    public function withProtocolVersion($version) : self
137
    {
138
        $this->validateProtocolVersion($version);
139
140
        $instance = clone $this;
141
        $instance->version = $version;
142
143
        return $instance;
144
    }
145
146
    /**
147
     * Verifies the protocol version.
148
     *
149
     * @throws InvalidArgumentException
150
     * @param  string  $version The version string MUST contain only the HTTP
151
     * version number (e.g., "1.1", "1.0").
152
     */
153
    private function validateProtocolVersion(string $version) : void
154
    {
155
        if (! in_array($version, ['1.1', '2.0'])) {
156
            throw new InvalidArgumentException("Invalid HTTP version: {$version}");
157
        }
158
    }
159
160
    /**
161
     * Retrieves all message header values.
162
     *
163
     * The keys represent the header name as it will be sent over the wire, and
164
     * each value is an array of strings associated with the header.
165
     *
166
     *     // Represent the headers as a string
167
     *     foreach ($message->getHeaders() as $name => $values) {
168
     *         echo $name . ": " . implode(", ", $values);
169
     *     }
170
     *
171
     *     // Emit headers iteratively:
172
     *     foreach ($message->getHeaders() as $name => $values) {
173
     *         foreach ($values as $value) {
174
     *             header(sprintf('%s: %s', $name, $value), false);
175
     *         }
176
     *     }
177
     *
178
     * While header names are not case-sensitive, getHeaders() will preserve the
179
     * exact case in which headers were originally specified.
180
     *
181
     * @return string[][] Returns an associative array of the message's headers. Each
182
     *     key MUST be a header name, and each value MUST be an array of strings
183
     *     for that header.
184
     */
185
    public function getHeaders()
186
    {
187
        return $this->headers;
188
    }
189
190
    /**
191
     * Checks if a header exists by the given case-insensitive name.
192
     *
193
     * @param  string  $name  Case-insensitive header field name.
194
     * @return bool Returns true if any header names match the given header
195
     *     name using a case-insensitive string comparison. Returns false if
196
     *     no matching header name is found in the message.
197
     */
198
    public function hasHeader($name)
199
    {
200
        return array_key_exists(
201
            mb_strtoupper($name),
202
            array_change_key_case($this->headers, CASE_UPPER)
203
        );
204
    }
205
206
    /**
207
     * Retrieves a message header value by the given case-insensitive name.
208
     *
209
     * This method returns an array of all the header values of the given
210
     * case-insensitive header name.
211
     *
212
     * If the header does not appear in the message, this method MUST return an
213
     * empty array.
214
     *
215
     * @param  string  $name  Case-insensitive header field name.
216
     * @return string[] An array of string values as provided for the given
217
     *    header. If the header does not appear in the message, this method MUST
218
     *    return an empty array.
219
     */
220
    public function getHeader($name)
221
    {
222
        $name = mb_strtoupper($name);
223
        $headers = array_change_key_case($this->headers, CASE_UPPER);
224
225
        if (array_key_exists($name, $headers) === false) {
226
            return [];
227
        }
228
229
        return $headers[$name];
230
    }
231
232
    /**
233
     * Retrieves a comma-separated string of the values for a single header.
234
     *
235
     * This method returns all of the header values of the given
236
     * case-insensitive header name as a string concatenated together using
237
     * a comma.
238
     *
239
     * NOTE: Not all header values may be appropriately represented using
240
     * comma concatenation. For such headers, use getHeader() instead
241
     * and supply your own delimiter when concatenating.
242
     *
243
     * If the header does not appear in the message, this method MUST return
244
     * an empty string.
245
     *
246
     * @param  string  $name  Case-insensitive header field name.
247
     * @return string A string of values as provided for the given header
248
     *    concatenated together using a comma. If the header does not appear in
249
     *    the message, this method MUST return an empty string.
250
     */
251
    public function getHeaderLine($name)
252
    {
253
        return implode(',', $this->getHeader($name));
254
    }
255
256
    /**
257
     * Return an instance with the provided value replacing the specified header.
258
     *
259
     * While header names are case-insensitive, the casing of the header will
260
     * be preserved by this function, and returned from getHeaders().
261
     *
262
     * This method MUST be implemented in such a way as to retain the
263
     * immutability of the message, and MUST return an instance that has the
264
     * new and/or updated header and value.
265
     *
266
     * @param  string  $name  Case-insensitive header field name.
267
     * @param  string|string[]  $value  Header value(s).
268
     * @return static
269
     * @throws InvalidArgumentException for invalid header names or values.
270
     */
271
    public function withHeader($name, $value)
272
    {
273
        $newHeaders = array_merge($this->getHeaders(), [$name => [$value]]);
274
275
        $instance = clone $this;
276
        $instance->headers = $newHeaders;
277
278
        return $instance;
279
    }
280
281
    /**
282
     * Return an instance with the specified header appended with the given value.
283
     *
284
     * Existing values for the specified header will be maintained. The new
285
     * value(s) will be appended to the existing list. If the header did not
286
     * exist previously, it will be added.
287
     *
288
     * This method MUST be implemented in such a way as to retain the
289
     * immutability of the message, and MUST return an instance that has the
290
     * new header and/or value.
291
     *
292
     * @param  string  $name  Case-insensitive header field name to add.
293
     * @param  string|string[]  $value  Header value(s).
294
     * @return static
295
     * @throws InvalidArgumentException for invalid header names or values.
296
     */
297
    public function withAddedHeader($name, $value)
298
    {
299
        $newHeaders = $this->getHeaders();
300
        $headerToUpdate = $this->getHeader($name);
301
        $headerToUpdate[] = $value;
302
        $newHeaders[$name] = $headerToUpdate;
303
304
        $instance = clone $this;
305
        $instance->headers = $newHeaders;
306
307
        return $instance;
308
    }
309
310
    /**
311
     * Return an instance without the specified header.
312
     *
313
     * Header resolution MUST be done without case-sensitivity.
314
     *
315
     * This method MUST be implemented in such a way as to retain the
316
     * immutability of the message, and MUST return an instance that removes
317
     * the named header.
318
     *
319
     * @param  string  $name  Case-insensitive header field name to remove.
320
     * @return static
321
     */
322
    public function withoutHeader($name)
323
    {
324
        $newHeaders = $this->getHeaders();
325
        unset($newHeaders[$name]);
326
327
        $instance = clone $this;
328
        $instance->headers = $newHeaders;
329
330
        return $instance;
331
    }
332
333
    /**
334
     * Verifies the headers are valid.
335
     *
336
     * @throws InvalidArgumentException
337
     * @param  array  $headers Headers for the incoming request
338
     */
339
    private function validateHeaders(array $headers) : void
340
    {
341
        $exceptionMessage = 'Invalid headers: '.json_encode($headers);
342
343
        if (empty($headers)) {
344
            return;
345
        }
346
347
        $headersWithArraysOnly = array_filter($headers, function ($header) {
348
            return is_array($header);
349
        });
350
351
        if (count($headers) !== count($headersWithArraysOnly)) {
352
            throw new InvalidArgumentException($exceptionMessage);
353
        }
354
355
        foreach ($headers as $key => $header) {
356
            $headerWithStringValuesOnly = array_filter($header, function ($headerValue) {
357
                return is_string($headerValue);
358
            });
359
360
            if (count($header) !== count($headerWithStringValuesOnly)) {
361
                throw new InvalidArgumentException($exceptionMessage);
362
            }
363
        }
364
    }
365
366
    /**
367
     * Gets the body of the message.
368
     *
369
     * @return StreamInterface Returns the body as a stream.
370
     */
371
    public function getBody()
372
    {
373
        return $this->body;
374
    }
375
376
    /**
377
     * Return an instance with the specified message body.
378
     *
379
     * The body MUST be a StreamInterface object.
380
     *
381
     * This method MUST be implemented in such a way as to retain the
382
     * immutability of the message, and MUST return a new instance that has the
383
     * new body stream.
384
     *
385
     * @param  StreamInterface  $body  Body.
386
     * @return static
387
     * @throws InvalidArgumentException When the body is not valid.
388
     */
389
    public function withBody(StreamInterface $body)
390
    {
391
        $instance = clone $this;
392
        $instance->body = $body;
393
394
        return $instance;
395
    }
396
397
    /**
398
     * Retrieves the message's request target.
399
     *
400
     * Retrieves the message's request-target either as it will appear (for
401
     * clients), as it appeared at request (for servers), or as it was
402
     * specified for the instance (see withRequestTarget()).
403
     *
404
     * In most cases, this will be the origin-form of the composed URI,
405
     * unless a value was provided to the concrete implementation (see
406
     * withRequestTarget() below).
407
     *
408
     * If no URI is available, and no request-target has been specifically
409
     * provided, this method MUST return the string "/".
410
     *
411
     * @return string
412
     */
413
    public function getRequestTarget()
414
    {
415
        return $this->requestTarget;
416
    }
417
418
    /**
419
     * Return an instance with the specific request-target.
420
     *
421
     * If the request needs a non-origin-form request-target — e.g., for
422
     * specifying an absolute-form, authority-form, or asterisk-form —
423
     * this method may be used to create an instance with the specified
424
     * request-target, verbatim.
425
     *
426
     * This method MUST be implemented in such a way as to retain the
427
     * immutability of the message, and MUST return an instance that has the
428
     * changed request target.
429
     *
430
     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
431
     *     request-target forms allowed in request messages)
432
     * @param  mixed  $requestTarget
433
     * @return static
434
     */
435
    public function withRequestTarget($requestTarget)
436
    {
437
        $instance = clone $this;
438
        $instance->requestTarget = (string) $requestTarget;
439
440
        return $instance;
441
    }
442
443
    /**
444
     * Verifies the HTTP method is valid.
445
     *
446
     * @throws InvalidArgumentException
447
     * @param  string  $method HTTP method for the incoming request
448
     */
449
    private function validateMethod(string $method) : void
450
    {
451
        if (! in_array(strtoupper($method), ['POST', 'GET', 'OPTIONS'])) {
452
            throw new InvalidArgumentException("Invalid HTTP method: {$method}");
453
        }
454
    }
455
456
    /**
457
     * Retrieves the HTTP method of the request.
458
     *
459
     * @return string Returns the request method.
460
     */
461
    public function getMethod()
462
    {
463
        return $this->method;
464
    }
465
466
    /**
467
     * Return an instance with the provided HTTP method.
468
     *
469
     * While HTTP method names are typically all uppercase characters, HTTP
470
     * method names are case-sensitive and thus implementations SHOULD NOT
471
     * modify the given string.
472
     *
473
     * This method MUST be implemented in such a way as to retain the
474
     * immutability of the message, and MUST return an instance that has the
475
     * changed request method.
476
     *
477
     * @param  string  $method  Case-sensitive method.
478
     * @return static
479
     * @throws InvalidArgumentException for invalid HTTP methods.
480
     */
481
    public function withMethod($method)
482
    {
483
        $this->validateMethod($method);
484
485
        $instance = clone $this;
486
        $instance->method = $method;
487
488
        return $instance;
489
    }
490
491
    /**
492
     * Retrieves the URI instance.
493
     *
494
     * This method MUST return a UriInterface instance.
495
     *
496
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
497
     * @return UriInterface Returns a UriInterface instance
498
     *     representing the URI of the request.
499
     */
500
    public function getUri()
501
    {
502
        return $this->uri;
503
    }
504
505
    /**
506
     * Returns an instance with the provided URI.
507
     *
508
     * This method MUST update the Host header of the returned request by
509
     * default if the URI contains a host component. If the URI does not
510
     * contain a host component, any pre-existing Host header MUST be carried
511
     * over to the returned request.
512
     *
513
     * You can opt-in to preserving the original state of the Host header by
514
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
515
     * `true`, this method interacts with the Host header in the following ways:
516
     *
517
     * - If the Host header is missing or empty, and the new URI contains
518
     *   a host component, this method MUST update the Host header in the returned
519
     *   request.
520
     * - If the Host header is missing or empty, and the new URI does not contain a
521
     *   host component, this method MUST NOT update the Host header in the returned
522
     *   request.
523
     * - If a Host header is present and non-empty, this method MUST NOT update
524
     *   the Host header in the returned request.
525
     *
526
     * This method MUST be implemented in such a way as to retain the
527
     * immutability of the message, and MUST return an instance that has the
528
     * new UriInterface instance.
529
     *
530
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
531
     * @param  UriInterface  $uri  New request URI to use.
532
     * @param  bool  $preserveHost  Preserve the original state of the Host header.
533
     * @return static
534
     */
535
    public function withUri(UriInterface $uri, $preserveHost = false)
536
    {
537
        $headers = $this->getHeaders();
538
        $currentUriHost = $this->uri->getHost();
539
540
        if ($preserveHost && $currentUriHost) {
541
            $headers['HTTP_HOST'] = [$currentUriHost];
542
        }
543
544
        $instance = clone $this;
545
        $instance->headers = $headers;
546
        $instance->uri = $uri;
547
548
        return $instance;
549
    }
550
551
    /**
552
     * Retrieve server parameters.
553
     *
554
     * Retrieves data related to the incoming request environment,
555
     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
556
     * REQUIRED to originate from $_SERVER.
557
     *
558
     * @return array
559
     */
560
    public function getServerParams()
561
    {
562
        return $this->serverParams;
563
    }
564
565
    /**
566
     * Retrieve cookies.
567
     *
568
     * Retrieves cookies sent by the client to the server.
569
     *
570
     * The data MUST be compatible with the structure of the $_COOKIE
571
     * superglobal.
572
     *
573
     * @return array
574
     */
575
    public function getCookieParams()
576
    {
577
        return $this->cookieParams;
578
    }
579
580
    /**
581
     * Return an instance with the specified cookies.
582
     *
583
     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
584
     * be compatible with the structure of $_COOKIE. Typically, this data will
585
     * be injected at instantiation.
586
     *
587
     * This method MUST NOT update the related Cookie header of the request
588
     * instance, nor related values in the server params.
589
     *
590
     * This method MUST be implemented in such a way as to retain the
591
     * immutability of the message, and MUST return an instance that has the
592
     * updated cookie values.
593
     *
594
     * @param  array  $cookies  Array of key/value pairs representing cookies.
595
     * @return static
596
     */
597
    public function withCookieParams(array $cookies)
598
    {
599
        $instance = clone $this;
600
        $instance->cookieParams = $cookies;
601
602
        return $instance;
603
    }
604
605
    /**
606
     * Retrieve query string arguments.
607
     *
608
     * Retrieves the deserialized query string arguments, if any.
609
     *
610
     * Note: the query params might not be in sync with the URI or server
611
     * params. If you need to ensure you are only getting the original
612
     * values, you may need to parse the query string from `getUri()->getQuery()`
613
     * or from the `QUERY_STRING` server param.
614
     *
615
     * @return array
616
     */
617
    public function getQueryParams()
618
    {
619
        return $this->queryParams;
620
    }
621
622
    /**
623
     * Return an instance with the specified query string arguments.
624
     *
625
     * These values SHOULD remain immutable over the course of the incoming
626
     * request. They MAY be injected during instantiation, such as from PHP's
627
     * $_GET superglobal, or MAY be derived from some other value such as the
628
     * URI. In cases where the arguments are parsed from the URI, the data
629
     * MUST be compatible with what PHP's parse_str() would return for
630
     * purposes of how duplicate query parameters are handled, and how nested
631
     * sets are handled.
632
     *
633
     * Setting query string arguments MUST NOT change the URI stored by the
634
     * request, nor the values in the server params.
635
     *
636
     * This method MUST be implemented in such a way as to retain the
637
     * immutability of the message, and MUST return an instance that has the
638
     * updated query string arguments.
639
     *
640
     * @param  array  $query  Array of query string arguments, typically from
641
     *     $_GET.
642
     * @return static
643
     */
644
    public function withQueryParams(array $query)
645
    {
646
        $instance = clone $this;
647
        $instance->queryParams = array_merge($this->getQueryParams(), $query);
648
649
        return $instance;
650
    }
651
652
    /**
653
     * Retrieve normalized file upload data.
654
     *
655
     * This method returns upload metadata in a normalized tree, with each leaf
656
     * an instance of Psr\Http\Message\UploadedFileInterface.
657
     *
658
     * These values MAY be prepared from $_FILES or the message body during
659
     * instantiation, or MAY be injected via withUploadedFiles().
660
     *
661
     * @return array An array tree of UploadedFileInterface instances; an empty
662
     *     array MUST be returned if no data is present.
663
     */
664
    public function getUploadedFiles()
665
    {
666
        return $this->uploadedFiles;
667
    }
668
669
    /**
670
     * Create a new instance with the specified uploaded files.
671
     *
672
     * This method MUST be implemented in such a way as to retain the
673
     * immutability of the message, and MUST return an instance that has the
674
     * updated body parameters.
675
     *
676
     * @param  array  $uploadedFiles  An array tree of UploadedFileInterface instances.
677
     * @return static
678
     * @throws InvalidArgumentException if an invalid structure is provided.
679
     */
680
    public function withUploadedFiles(array $uploadedFiles)
681
    {
682
        $filteredUploadedFiles = array_filter($uploadedFiles, function ($uploadedFile) {
683
            return $uploadedFile instanceof UploadedFileInterface;
684
        });
685
686
        if (count($filteredUploadedFiles) !== count($uploadedFiles)) {
687
            throw new InvalidArgumentException(
688
                'Must be an array with instances of '
689
                .UploadedFileInterface::class
690
            );
691
        }
692
693
        $instance = clone $this;
694
        $instance->uploadedFiles = $uploadedFiles;
695
696
        return $instance;
697
    }
698
699
    /**
700
     * Retrieve any parameters provided in the request body.
701
     *
702
     * If the request Content-Type is either application/x-www-form-urlencoded
703
     * or multipart/form-data, and the request method is POST, this method MUST
704
     * return the contents of $_POST.
705
     *
706
     * Otherwise, this method may return any results of deserializing
707
     * the request body content; as parsing returns structured content, the
708
     * potential types MUST be arrays or objects only. A null value indicates
709
     * the absence of body content.
710
     *
711
     * @return null|array|object The deserialized body parameters, if any.
712
     *     These will typically be an array or object.
713
     */
714
    public function getParsedBody()
715
    {
716
        $isPost = $this->isPostRequest();
717
718
        if ($isPost) {
719
            return $_POST;
720
        }
721
722
        return $this->parsedBody;
723
    }
724
725
    /**
726
     * Return an instance with the specified body parameters.
727
     *
728
     * These MAY be injected during instantiation.
729
     *
730
     * If the request Content-Type is either application/x-www-form-urlencoded
731
     * or multipart/form-data, and the request method is POST, use this method
732
     * ONLY to inject the contents of $_POST.
733
     *
734
     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
735
     * deserializing the request body content. Deserialization/parsing returns
736
     * structured data, and, as such, this method ONLY accepts arrays or objects,
737
     * or a null value if nothing was available to parse.
738
     *
739
     * As an example, if content negotiation determines that the request data
740
     * is a JSON payload, this method could be used to create a request
741
     * instance with the deserialized parameters.
742
     *
743
     * This method MUST be implemented in such a way as to retain the
744
     * immutability of the message, and MUST return an instance that has the
745
     * updated body parameters.
746
     *
747
     * @param  null|array|object  $data  The deserialized body data. This will
748
     *     typically be in an array or object.
749
     * @return static
750
     * @throws InvalidArgumentException if an unsupported argument type is
751
     *     provided.
752
     */
753
    public function withParsedBody($data)
754
    {
755
        /**
756
         * @psalm-suppress DocblockTypeContradiction
757
         * @psalm-suppress TypeDoesNotContainType
758
         */
759
        if (! is_null($data) && ! is_object($data) && ! is_array($data)) {
760
            throw new InvalidArgumentException(
761
                'Parsed body must be of type: null, array, or object'
762
            );
763
        }
764
765
        $data = (array) $data;
766
        $isPost = $this->isPostRequest();
767
        $instance = clone $this;
768
        $instance->parsedBody = array_merge((array) $this->getParsedBody(), $data);
769
770
        if ($isPost) {
771
            // TODO: identify potential risk with assigning values to the super global variable
772
            $_POST = array_merge($_POST, $data);
773
        }
774
775
        return $instance;
776
    }
777
778
    /**
779
     * Retrieve attributes derived from the request.
780
     *
781
     * The request "attributes" may be used to allow injection of any
782
     * parameters derived from the request: e.g., the results of path
783
     * match operations; the results of decrypting cookies; the results of
784
     * deserializing non-form-encoded message bodies; etc. Attributes
785
     * will be application and request specific, and CAN be mutable.
786
     *
787
     * @return array Attributes derived from the request.
788
     */
789
    public function getAttributes()
790
    {
791
        return $this->attributes;
792
    }
793
794
    /**
795
     * Retrieve a single derived request attribute.
796
     *
797
     * Retrieves a single derived request attribute as described in
798
     * getAttributes(). If the attribute has not been previously set, returns
799
     * the default value as provided.
800
     *
801
     * This method obviates the need for a hasAttribute() method, as it allows
802
     * specifying a default value to return if the attribute is not found.
803
     *
804
     * @param  string  $name  The attribute name.
805
     * @param  mixed  $default  Default value to return if the attribute does not exist.
806
     * @return mixed
807
     * @see getAttributes()
808
     */
809
    public function getAttribute($name, $default = null)
810
    {
811
        return $this->attributes[$name] ?? $default;
812
    }
813
814
    /**
815
     * Return an instance with the specified derived request attribute.
816
     *
817
     * This method allows setting a single derived request attribute as
818
     * described in getAttributes().
819
     *
820
     * This method MUST be implemented in such a way as to retain the
821
     * immutability of the message, and MUST return an instance that has the
822
     * updated attribute.
823
     *
824
     * @param  string  $name  The attribute name.
825
     * @param  mixed  $value  The value of the attribute.
826
     * @return static
827
     * @see getAttributes()
828
     */
829
    public function withAttribute($name, $value)
830
    {
831
        $instance = clone $this;
832
        $instance->attributes[$name] = $value;
833
834
        return $instance;
835
    }
836
837
    /**
838
     * Return an instance that removes the specified derived request attribute.
839
     *
840
     * This method allows removing a single derived request attribute as
841
     * described in getAttributes().
842
     *
843
     * This method MUST be implemented in such a way as to retain the
844
     * immutability of the message, and MUST return an instance that removes
845
     * the attribute.
846
     *
847
     * @param  string  $name  The attribute name.
848
     * @return static
849
     * @see getAttributes()
850
     */
851
    public function withoutAttribute($name)
852
    {
853
        $instance = clone $this;
854
855
        unset($instance->attributes[$name]);
856
857
        return $instance;
858
    }
859
860
    /**
861
     * Determines if the request is a POST request based on content type headers.
862
     * @return bool
863
     */
864
    private function isPostRequest() : bool
865
    {
866
        foreach ($this->getHeader('content-type') as $contentType) {
867
            if ($contentType === 'application/x-www-form-urlencoded' ||
868
                $contentType === 'multipart/form-data') {
869
                return true;
870
            }
871
        }
872
873
        return false;
874
    }
875
}
876