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 ( 331363...4b35f6 )
by Patrique
04:28
created

ServerRequest::withParsedBody()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.2728
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
final class ServerRequest implements ServerRequestInterface
14
{
15
    /**
16
     * @var string Represent the HTTP version number (e.g., "1.1", "1.0")
17
     */
18
    private string $version;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
19
20
    /**
21
     * @var array<array> Contains header by key and array.
22
     * e.g. ['content-type' => ['application/json']]
23
     */
24
    private array $headers;
25
26
    /** @var StreamInterface */
27
    private StreamInterface $body;
28
29
    /** @var string */
30
    private string $requestTarget;
31
32
    /** @var string */
33
    private string $method;
34
35
    /** @var UriInterface */
36
    private UriInterface $uri;
37
38
    /** @var array<mixed> */
39
    private array $serverParams;
40
41
    /** @var array<mixed> */
42
    private array $cookieParams;
43
44
    /** @var array<mixed> */
45
    private array $queryParams;
46
47
    /** @var array<UploadedFileInterface> */
48
    private array $uploadedFiles;
49
50
    /** @var null|array<mixed>|object */
51
    private $parsedBody;
52
53
    /** @var array<mixed> */
54
    private array $attributes;
55
56
    /**
57
     * ServerRequest constructor.
58
     * @param string                       $version
59
     * @param array<array>                 $headers
60
     * @param StreamInterface              $body
61
     * @param string                       $requestTarget
62
     * @param string                       $method
63
     * @param UriInterface                 $uri
64
     * @param array<mixed>                 $serverParams
65
     * @param array<mixed>                 $cookieParams
66
     * @param array<string>                $queryParams
67
     * @param array<UploadedFileInterface> $uploadedFiles
68
     */
69
    public function __construct(
70
        string $version,
71
        array $headers,
72
        StreamInterface $body,
73
        string $requestTarget,
74
        string $method,
75
        UriInterface $uri,
76
        array $serverParams,
77
        array $cookieParams,
78
        array $queryParams,
79
        array $uploadedFiles
80
    ) {
81
        $this->validateProtocolVersion($version);
82
        $this->validateHeaders($headers);
83
        $this->validateMethod($method);
84
        $this->validateUploadedFiles($uploadedFiles);
85
86
        $this->version = $version;
87
        /** @psalm-suppress MixedPropertyTypeCoercion */
88
        $this->headers = $headers;
89
        $this->body = $body;
90
        $this->requestTarget = $requestTarget;
91
        $this->method = $method;
92
        $this->uri = $uri;
93
        $this->serverParams = $serverParams;
94
        $this->cookieParams = $cookieParams;
95
        $this->queryParams = $queryParams;
96
        /** @psalm-suppress MixedPropertyTypeCoercion */
97
        $this->uploadedFiles = $uploadedFiles;
98
        $this->attributes = [];
99
    }
100
101
    /**
102
     * Create instance of server request based on global values.
103
     * @return static
104
     */
105
    public static function makeWithGlobals(): self
106
    {
107
        $headers = Headers::getHeadersArrayFromGlobals();
108
        $protocolVersion = strval($_SERVER['SERVER_PROTOCOL'] ?? '1.1');
109
        $protocolVersion = str_replace('HTTP/', '', $protocolVersion);
110
        $requestTarget = strval($_SERVER['REQUEST_URI'] ?? '/');
111
        $method = strval($_SERVER['REQUEST_METHOD'] ?? 'GET');
112
113
        // TODO: identify potential risk of using globals
114
        return new static(
115
            $protocolVersion,
116
            $headers,
117
            new Stream('body'),
118
            $requestTarget,
119
            $method,
120
            new Uri($requestTarget),
121
            $_SERVER,
122
            $_COOKIE,
123
            $_GET,
124
            $_FILES
125
        );
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function getProtocolVersion()
132
    {
133
        return $this->version;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function withProtocolVersion($version) : self
140
    {
141
        $this->validateProtocolVersion($version);
142
143
        $instance = clone $this;
144
        $instance->version = $version;
145
146
        return $instance;
147
    }
148
149
    /**
150
     * Verifies the protocol version.
151
     *
152
     * @throws InvalidArgumentException
153
     * @param  string  $version The version string MUST contain only the HTTP
154
     * version number (e.g., "1.1", "1.0").
155
     */
156
    private function validateProtocolVersion(string $version) : void
157
    {
158
        if (! in_array($version, ['1.1', '2.0'])) {
159
            throw new InvalidArgumentException("Invalid HTTP version: {$version}");
160
        }
161
    }
162
163
    /**
164
     * @psalm-suppress MixedReturnTypeCoercion
165
     * {@inheritdoc}
166
     */
167
    public function getHeaders()
168
    {
169
        return $this->headers;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function hasHeader($name)
176
    {
177
        return array_key_exists(
178
            mb_strtoupper($name),
179
            array_change_key_case($this->headers, CASE_UPPER)
180
        );
181
    }
182
183
    /**
184
     * @psalm-suppress MixedReturnTypeCoercion
185
     * {@inheritdoc}
186
     */
187
    public function getHeader($name)
188
    {
189
        $name = mb_strtoupper($name);
190
        $headers = array_change_key_case($this->headers, CASE_UPPER);
191
192
        if (array_key_exists($name, $headers) === false) {
193
            return [];
194
        }
195
196
        return $headers[$name];
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202
    public function getHeaderLine($name)
203
    {
204
        return implode(',', $this->getHeader($name));
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210
    public function withHeader($name, $value)
211
    {
212
        $newHeaders = array_merge($this->getHeaders(), [$name => [$value]]);
213
214
        $instance = clone $this;
215
        $instance->headers = $newHeaders;
216
217
        return $instance;
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function withAddedHeader($name, $value)
224
    {
225
        $newHeaders = $this->getHeaders();
226
        $headerToUpdate = $this->getHeader($name);
227
        $headerToUpdate[] = $value;
228
        $newHeaders[$name] = $headerToUpdate;
229
230
        $instance = clone $this;
231
        $instance->headers = $newHeaders;
232
233
        return $instance;
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function withoutHeader($name)
240
    {
241
        $newHeaders = $this->getHeaders();
242
        unset($newHeaders[$name]);
243
244
        $instance = clone $this;
245
        $instance->headers = $newHeaders;
246
247
        return $instance;
248
    }
249
250
    /**
251
     * Verifies the headers are valid.
252
     *
253
     * @throws InvalidArgumentException
254
     * @param  array<array> $headers Headers for the incoming request
255
     */
256
    private function validateHeaders(array $headers) : void
257
    {
258
        $exceptionMessage = 'Invalid headers: '.json_encode($headers);
259
260
        if (empty($headers)) {
261
            return;
262
        }
263
264
        $headersWithArraysOnly = array_filter($headers, function ($header) {
265
            return is_array($header);
266
        });
267
268
        if (count($headers) !== count($headersWithArraysOnly)) {
269
            throw new InvalidArgumentException($exceptionMessage);
270
        }
271
272
        foreach ($headers as $key => $header) {
273
            $headerWithStringValuesOnly = array_filter($header, function ($headerValue) {
274
                return is_string($headerValue);
275
            });
276
277
            if (count($header) !== count($headerWithStringValuesOnly)) {
278
                throw new InvalidArgumentException($exceptionMessage);
279
            }
280
        }
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286
    public function getBody()
287
    {
288
        return $this->body;
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294
    public function withBody(StreamInterface $body)
295
    {
296
        $instance = clone $this;
297
        $instance->body = $body;
298
299
        return $instance;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function getRequestTarget()
306
    {
307
        return $this->requestTarget;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313
    public function withRequestTarget($requestTarget)
314
    {
315
        $instance = clone $this;
316
        $instance->requestTarget = (string) $requestTarget;
317
318
        return $instance;
319
    }
320
321
    /**
322
     * Verifies the HTTP method is valid.
323
     *
324
     * @throws InvalidArgumentException
325
     * @param  string  $method HTTP method for the incoming request
326
     */
327
    private function validateMethod(string $method) : void
328
    {
329
        if (! in_array(strtoupper($method), ['POST', 'GET', 'OPTIONS'])) {
330
            throw new InvalidArgumentException("Invalid HTTP method: {$method}");
331
        }
332
    }
333
334
    /**
335
     * {@inheritdoc}
336
     */
337
    public function getMethod()
338
    {
339
        return $this->method;
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function withMethod($method)
346
    {
347
        $this->validateMethod($method);
348
349
        $instance = clone $this;
350
        $instance->method = $method;
351
352
        return $instance;
353
    }
354
355
    /**
356
     * {@inheritdoc}
357
     */
358
    public function getUri()
359
    {
360
        return $this->uri;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366
    public function withUri(UriInterface $uri, $preserveHost = false)
367
    {
368
        $headers = $this->getHeaders();
369
        $currentUriHost = $this->uri->getHost();
370
371
        if ($preserveHost && $currentUriHost) {
372
            $headers['HTTP_HOST'] = [$currentUriHost];
373
        }
374
375
        $instance = clone $this;
376
        $instance->headers = $headers;
377
        $instance->uri = $uri;
378
379
        return $instance;
380
    }
381
382
    /**
383
     * @return array<mixed>
384
     */
385
    public function getServerParams()
386
    {
387
        return $this->serverParams;
388
    }
389
390
    /**
391
     * @return array<mixed>
392
     * @see ServerRequestInterface::getCookieParams()
393
     */
394
    public function getCookieParams()
395
    {
396
        return $this->cookieParams;
397
    }
398
399
    /**
400
     * @param array<mixed> $cookies Array of key/value pairs representing cookies.
401
     * @return static
402
     * @see ServerRequestInterface::withCookieParams()
403
     */
404
    public function withCookieParams(array $cookies)
405
    {
406
        $instance = clone $this;
407
        $instance->cookieParams = $cookies;
408
409
        return $instance;
410
    }
411
412
    /**
413
     * @return array<string>
414
     * @see ServerRequestInterface::getQueryParams()
415
     */
416
    public function getQueryParams()
417
    {
418
        return $this->queryParams;
419
    }
420
421
    /**
422
     * @param string[] $query Array of query string arguments, typically from
423
     *     $_GET.
424
     * @return static
425
     * @see ServerRequestInterface::withQueryParams()
426
     */
427
    public function withQueryParams(array $query)
428
    {
429
        $instance = clone $this;
430
        $instance->queryParams = array_merge($this->getQueryParams(), $query);
431
432
        return $instance;
433
    }
434
435
    /**
436
     * @return array<UploadedFileInterface> An array tree of UploadedFileInterface instances;
437
     * an empty array MUST be returned if no data is present.
438
     *@see ServerRequestInterface::getUploadedFiles()
439
     */
440
    public function getUploadedFiles()
441
    {
442
        return $this->uploadedFiles;
443
    }
444
445
    /**
446
     * @psalm-suppress MoreSpecificImplementedParamType
447
     * @param array<UploadedFileInterface> $uploadedFiles An array tree of UploadedFileInterface instances.
448
     * @return static
449
     * @throws \InvalidArgumentException if an invalid structure is provided.
450
     * @see ServerRequestInterface::withUploadedFiles()
451
     */
452
    public function withUploadedFiles(array $uploadedFiles)
453
    {
454
        $uploadedFiles = $this->validateUploadedFiles($uploadedFiles);
455
456
        $instance = clone $this;
457
        /** @psalm-suppress MixedPropertyTypeCoercion */
458
        $instance->uploadedFiles = $uploadedFiles;
459
460
        return $instance;
461
    }
462
463
    /**
464
     * @param array<UploadedFileInterface> $uploadedFiles
465
     * @return array<UploadedFileInterface>
466
     */
467
    private function validateUploadedFiles(array $uploadedFiles) : array
468
    {
469
        /** @psalm-suppress RedundantConditionGivenDocblockType */
470
        $filteredUploadedFiles = array_filter($uploadedFiles, function ($uploadedFile) {
471
            return $uploadedFile instanceof UploadedFileInterface;
472
        });
473
474
        if (count($filteredUploadedFiles) !== count($uploadedFiles)) {
475
            throw new InvalidArgumentException(
476
                'Must be an array with instances of '
477
                .UploadedFileInterface::class
478
            );
479
        }
480
481
        return $filteredUploadedFiles;
482
    }
483
484
    /**
485
     * @return null|array<mixed>|object $data
486
     * @see ServerRequestInterface::getParsedBody()
487
     */
488
    public function getParsedBody()
489
    {
490
        $isPost = $this->isPostRequest();
491
492
        if ($isPost) {
493
            return $_POST;
494
        }
495
496
        return $this->parsedBody;
497
    }
498
499
    /**
500
     * @param null|array<mixed>|object $data
501
     * @return static
502
     * @see ServerRequestInterface::withParsedBody()
503
     */
504
    public function withParsedBody($data)
505
    {
506
        /** @psalm-suppress DocblockTypeContradiction */
507
        if (! is_null($data) && ! is_object($data) && ! is_array($data)) {
508
            throw new InvalidArgumentException(
509
                'Parsed body must be of type: null, array, or object'
510
            );
511
        }
512
513
        $data = (array) $data;
514
        $isPost = $this->isPostRequest();
515
        $instance = clone $this;
516
        $instance->parsedBody = array_merge((array) $this->getParsedBody(), $data);
517
518
        if ($isPost) {
519
            // TODO: identify potential risk with assigning values to the super global variable
520
            $_POST = array_merge($_POST, $data);
521
        }
522
523
        return $instance;
524
    }
525
526
    /**
527
     * @return array<mixed>
528
     * @see ServerRequestInterface::getAttributes()
529
     */
530
    public function getAttributes()
531
    {
532
        return $this->attributes;
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538
    public function getAttribute($name, $default = null)
539
    {
540
        return $this->attributes[$name] ?? $default;
541
    }
542
543
    /**
544
     * {@inheritdoc}
545
     */
546
    public function withAttribute($name, $value)
547
    {
548
        $instance = clone $this;
549
        $instance->attributes[$name] = $value;
550
551
        return $instance;
552
    }
553
554
    /**
555
     * {@inheritdoc}
556
     */
557
    public function withoutAttribute($name)
558
    {
559
        $instance = clone $this;
560
561
        unset($instance->attributes[$name]);
562
563
        return $instance;
564
    }
565
566
    /**
567
     * Determines if the request is a POST request based on content type headers.
568
     * @return bool
569
     */
570
    private function isPostRequest() : bool
571
    {
572
        foreach ($this->getHeader('content-type') as $contentType) {
573
            if ($contentType === 'application/x-www-form-urlencoded' ||
574
                $contentType === 'multipart/form-data') {
575
                return true;
576
            }
577
        }
578
579
        return false;
580
    }
581
}
582