Request   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 516
Duplicated Lines 0 %

Test Coverage

Coverage 68.38%

Importance

Changes 13
Bugs 3 Features 0
Metric Value
eloc 190
c 13
b 3
f 0
dl 0
loc 516
ccs 93
cts 136
cp 0.6838
rs 5.04
wmc 57

36 Methods

Rating   Name   Duplication   Size   Complexity  
A setContentType() 0 10 2
A isOK() 0 3 1
A addHeader() 0 5 1
A __construct() 0 4 1
A setMethod() 0 11 2
A setRequestUri() 0 5 1
A hasParam() 0 4 2
A redirectWithError() 0 3 1
A isClientError() 0 3 2
A setBody() 0 5 1
A getMethod() 0 3 1
A write() 0 25 5
A getBody() 0 3 1
A getHeaders() 0 3 1
A getHttpCode() 0 3 1
A redirectWithInfo() 0 3 1
A isServerError() 0 3 2
A getPath() 0 3 1
A getStringForHttpCode() 0 9 2
A redirect() 0 7 1
A setHttpCode() 0 5 1
A isRedirect() 0 3 2
A getUrl() 0 3 1
A setHeaders() 0 5 1
A flashData() 0 5 1
A parse() 0 17 4
A getPostParam() 0 6 2
A getRequestUri() 0 3 1
A getParam() 0 10 3
A getQuery() 0 3 1
A parseRemoteIp() 0 20 5
A flash() 0 5 1
A getRemoteIp() 0 3 1
A redirectWithSuccess() 0 3 1
A redirectWithData() 0 3 1
A setUrl() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Request often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Request, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Suricate;
6
7
use InvalidArgumentException;
8
9
/**
10
 * Request class
11
 *
12
 * @SuppressWarnings("StaticAccess")
13
 */
14
class Request
15
{
16
    const HTTP_METHOD_GET = 'GET';
17
    const HTTP_METHOD_POST = 'POST';
18
    const HTTP_METHOD_PUT = 'PUT';
19
    const HTTP_METHOD_DELETE = 'DELETE';
20
    const HTTP_METHOD_HEAD = 'HEAD';
21
    const HTTP_METHOD_OPTIONS = 'OPTIONS';
22
    const HTTP_METHOD_PATCH = 'PATCH';
23
24
    private $method = self::HTTP_METHOD_GET;
25
    private $methods = [
26
        self::HTTP_METHOD_GET => 'GET',
27
        self::HTTP_METHOD_POST => 'POST',
28
        self::HTTP_METHOD_PUT => 'PUT',
29
        self::HTTP_METHOD_DELETE => 'DELETE',
30
        self::HTTP_METHOD_HEAD => 'HEAD',
31
        self::HTTP_METHOD_OPTIONS => 'OPTIONS',
32
        self::HTTP_METHOD_PATCH => 'PATCH'
33
    ];
34
35
    private $httpCodeString = [
36
        100 => 'Continue',
37
        101 => 'Switching Protocols',
38
        102 => 'Processing',
39
        200 => 'OK',
40
        201 => 'Created',
41
        202 => 'Accepted',
42
        203 => 'Non-Authoritative Information',
43
        204 => 'No Content',
44
        205 => 'Reset Content',
45
        206 => 'Partial Content',
46
        207 => 'Multi-Status',
47
        208 => 'Already Reported',
48
        226 => 'IM Used',
49
        250 => 'Low on Storage Space',
50
        300 => 'Multiple Choices',
51
        301 => 'Moved Permanently',
52
        302 => 'Found',
53
        303 => 'See Other',
54
        304 => 'Not Modified',
55
        305 => 'Use Proxy',
56
        306 => '306 Switch Proxy',
57
        307 => 'Temporary Redirect',
58
        308 => 'Permanent Redirect',
59
        400 => 'Bad Request',
60
        401 => 'Unauthorized',
61
        402 => 'Payment Required',
62
        403 => 'Forbidden',
63
        404 => 'Not Found',
64
        405 => 'Method Not Allowed',
65
        406 => 'Not Acceptable',
66
        407 => 'Proxy Authentication Required',
67
        408 => 'Request Timeout',
68
        409 => 'Conflict',
69
        410 => 'Gone',
70
        411 => 'Length Required',
71
        412 => 'Precondition Failed',
72
        413 => 'Request Entity Too Large',
73
        414 => 'Request-URI Too Long',
74
        415 => 'Unsupported Media Type',
75
        416 => 'Requested Range Not Satisfiable',
76
        417 => 'Expectation Failed',
77
        422 => 'Unprocessable Entity',
78
        423 => 'Locked',
79
        424 => 'Failed Dependency',
80
        425 => 'Unordered Collection',
81
        426 => 'Upgrade Required',
82
        428 => 'Precondition Required',
83
        429 => 'Too Many Requests',
84
        431 => 'Request Header Fields Too Large',
85
        444 => 'No Response',
86
        449 => 'Retry With',
87
        450 => 'Blocked by Windows Parental Controls',
88
        494 => 'Request Header Too Large',
89
        495 => 'Cert Error',
90
        496 => 'No Cert',
91
        497 => 'HTTP to HTTPS',
92
        499 => 'Client Closed Request',
93
        500 => 'Internal Server Error',
94
        501 => 'Not Implemented',
95
        502 => 'Bad Gateway',
96
        503 => 'Service Unavailable',
97
        504 => 'Gateway Timeout',
98
        505 => 'HTTP Version Not Supported',
99
        506 => 'Variant Also Negotiates',
100
        507 => 'Insufficient Storage',
101
        508 => 'Loop Detected',
102
        509 => 'Bandwidth Limit Exceeded',
103
        510 => 'Not Extended',
104
        511 => 'Network Authentication Required'
105
    ];
106
107
    private $httpCode;
108
    private $headers = [];
109
    private $requestUri = '';
110
    private $remoteIp;
111
    private $url;
112
    private $body;
113
    private $path;
114
    private $query;
115
116
    /**
117 18
     * Request constructor
118
     */
119 18
    public function __construct()
120 18
    {
121 18
        $this->headers = [];
122
        $this->httpCode = 200;
123
    }
124
125
    /**
126
     * Parse server request
127
     *
128
     * @return void
129
     *
130 9
     * @SuppressWarnings("PHPMD.Superglobals")
131
     */
132 9
    public function parse()
133 5
    {
134 5
        if (isset($_SERVER['REQUEST_URI'])) {
135 5
            $this->setRequestUri($_SERVER['REQUEST_URI']);
136 5
            $parseResult = parse_url($_SERVER['REQUEST_URI']);
137
            $this->path = dataGet($parseResult, 'path');
138
            $this->query = dataGet($parseResult, 'query');
139 9
        }
140 1
141
        if (isset($_SERVER['REQUEST_METHOD'])) {
142 9
            $this->setMethod($_SERVER['REQUEST_METHOD']);
143 5
        }
144
        if (isset($_POST['_method'])) {
145
            $this->setMethod($_POST['_method']);
146 9
        }
147 9
148
        $this->parseRemoteIp();
149
    }
150
151
    /**
152
     * Parse request and extract remote IP
153
     *
154
     * @return void
155
     *
156 9
     * @SuppressWarnings("PHPMD.Superglobals")
157
     */
158
    private function parseRemoteIp()
159
    {
160 9
        // FIXME: check for trusted_proxy and void forged header
161 9
        if (
162
            array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) &&
163
            !empty($_SERVER['HTTP_X_FORWARDED_FOR'])
164
        ) {
165
            if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') > 0) {
166
                $addr = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']);
167
                $this->remoteIp = trim($addr[0]);
168
                return;
169
            }
170
            $this->remoteIp = $_SERVER['HTTP_X_FORWARDED_FOR'];
171
            return;
172 9
        }
173
174
        if (isset($_SERVER['REMOTE_ADDR'])) {
175 9
            $this->remoteIp = $_SERVER['REMOTE_ADDR'];
176
        }
177
        return;
178
    }
179
180
    /**
181
     * Set Request method
182
     *
183
     * @param string $method Request method
184
     * @return Request
185
     *
186 7
     * @throws InvalidArgumentException
187
     */
188 7
    public function setMethod($method): Request
189 1
    {
190 1
        if (!isset($this->methods[$method])) {
191
            throw new InvalidArgumentException(
192
                'Invalid HTTP Method ' . $method
193
            );
194 7
        }
195
196 7
        $this->method = $method;
197
198
        return $this;
199
    }
200
201
    /**
202
     * Get Request method
203
     *
204 3
     * @return string
205
     */
206 3
    public function getMethod(): string
207
    {
208
        return $this->method;
209
    }
210
211
    /**
212
     * Set Request URL
213
     *
214
     * @param string $url
215
     *
216 2
     * @return Request
217
     */
218 2
    public function setUrl($url): Request
219
    {
220 2
        $this->url = $url;
221
222
        return $this;
223
    }
224
225
    /**
226
     * Get Request URL
227
     *
228 2
     * @return string|null
229
     */
230 2
    public function getUrl(): ?string
231
    {
232
        return $this->url;
233
    }
234
235
    /**
236
     * Get Remote IP Address
237
     *
238
     * @return string|null
239
     */
240
    public function getRemoteIp(): ?string
241
    {
242
        return $this->remoteIp;
243 10
    }
244
245 10
    public function setRequestUri($uri)
246
    {
247 10
        $this->requestUri = $uri;
248
249
        return $this;
250 11
    }
251
252 11
    public function getRequestUri()
253
    {
254
        return $this->requestUri;
255 1
    }
256
257 1
    public function getPath()
258
    {
259
        return $this->path;
260 1
    }
261
262 1
    public function getQuery()
263
    {
264
        return $this->query;
265
    }
266
267
    /**
268
     * Get POST parameter
269
     *
270
     * @param string $variable     Parameter name
271
     * @param mixed  $defaultValue Fallback value when parameter not set
272
     *
273
     * @return mixed
274
     *
275 1
     * @SuppressWarnings("PHPMD.Superglobals")
276
     */
277 1
    public static function getPostParam($variable, $defaultValue = null)
278 1
    {
279
        if (array_key_exists($variable, $_POST)) {
280 1
            return $_POST[$variable];
281
        }
282
        return $defaultValue;
283
    }
284
285
    /**
286
     * Get Request parameter, GET first, then POST
287
     *
288
     * @param string $variable     Parameter name
289
     * @param mixed  $defaultValue Fallback value when parameter not set
290
     *
291
     * @return mixed
292
     *
293 1
     * @SuppressWarnings("PHPMD.Superglobals")
294
     */
295 1
    public static function getParam($variable, $defaultValue = null)
296 1
    {
297
        if (array_key_exists($variable, $_GET)) {
298 1
            return $_GET[$variable];
299 1
        }
300
        if (array_key_exists($variable, $_POST)) {
301
            return $_POST[$variable];
302 1
        }
303
304
        return $defaultValue;
305
    }
306
307
    /**
308
     * Check if parameter exists in Request
309
     *
310
     * @param string $variable parameter name
311
     *
312
     * @return boolean
313
     *
314 1
     * @SuppressWarnings("PHPMD.Superglobals")
315
     */
316 1
    public static function hasParam($variable): bool
317 1
    {
318
        return array_key_exists($variable, $_GET) ||
319
            array_key_exists($variable, $_POST);
320
    }
321
322
    /**
323
     * Set request headers
324
     *
325
     * @param array $headers Headers to set key => $value
326
     *
327 1
     * @return Request
328
     */
329 1
    public function setHeaders(array $headers): Request
330
    {
331 1
        $this->headers = $headers;
332
333
        return $this;
334
    }
335
336
    /**
337
     * Add specific header
338
     *
339
     * @param string $header header name
340
     * @param string $value  header value
341 1
     * @return Request
342
     */
343 1
    public function addHeader($header, $value): Request
344
    {
345 1
        $this->headers[$header] = $value;
346
347
        return $this;
348
    }
349
350
    /**
351
     * Get request headers
352
     *
353 1
     * @return array
354
     */
355 1
    public function getHeaders(): array
356
    {
357
        return $this->headers;
358 1
    }
359
360
    public function setContentType(
361
        string $contentType,
362 1
        $encoding = null
363 1
    ): Request {
364
        if ($encoding !== null) {
365 1
            $contentType .= '; charset=' . $encoding;
366
        }
367 1
        $this->addHeader('Content-type', $contentType);
368
369
        return $this;
370 1
    }
371
372 1
    public function setBody(?string $body): Request
373
    {
374 1
        $this->body = $body;
375
376
        return $this;
377 1
    }
378
379 1
    public function getBody(): ?string
380
    {
381
        return $this->body;
382
    }
383
384
    public function write()
385
    {
386
        if (!headers_sent()) {
387
            if (substr(php_sapi_name(), 0, 3) == 'cgi') {
388
                $headerString = 'Status: ' . $this->getStringForHttpCode();
389
            } else {
390
                $headerString = 'HTTP/1.1 ' . $this->getStringForHttpCode();
391
            }
392
393
            header($headerString);
394
395
            // Send headers
396
            foreach ($this->headers as $headerName => $headerValue) {
397
                header($headerName . ':' . $headerValue);
398
            }
399
        }
400
401
        /**
402
         TODO HANDLE HTTP RESPONSE CODE
403
         */
404
        if ($this->httpCode !== null) {
405
        }
406
407
        // Send body
408
        echo $this->body;
409
    }
410
411
    //
412
    // HTTP Code
413 1
    //
414
415 1
    public function setHttpCode($code): Request
416
    {
417 1
        $this->httpCode = $code;
418
419
        return $this;
420 1
    }
421
422 1
    public function getHttpCode()
423
    {
424
        return $this->httpCode;
425
    }
426
427
    /**
428
     * Flash message
429
     *
430
     * @param string $type message type
431
     * @param array|string $data data to be flashed
432
     */
433
    public function flash(string $type, $data)
434
    {
435
        Flash::writeMessage($type, $data);
436
437
        return $this;
438
    }
439
440
    public function flashData($name, $value)
441
    {
442
        Flash::writeData($name, $value);
443
444
        return $this;
445
    }
446
447
    public function redirect($url, $httpCode = 302)
448
    {
449
        $this->setHttpCode($httpCode);
450
        $this->addHeader('Location', $url);
451
452
        $this->write();
453
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
454
    }
455
456
    public function redirectWithSuccess($url, $message)
457
    {
458
        $this->flash('success', $message)->redirect($url);
459
    }
460
461
    public function redirectWithInfo($url, $message)
462
    {
463
        $this->flash('info', $message)->redirect($url);
464
    }
465
466
    public function redirectWithError($url, $message)
467
    {
468
        $this->flash('error', $message)->redirect($url);
469
    }
470
471
    public function redirectWithData($url, $key, $value)
472
    {
473
        $this->flashData($key, $value)->redirect($url);
474
    }
475
476
    /**
477
     * Check if request has a 200 OK Code
478
     *
479 1
     * @return boolean
480
     */
481 1
    public function isOK(): bool
482
    {
483
        return $this->httpCode == 200;
484
    }
485
486
    /**
487
     * Check if request has a 3XX HTTP code
488
     *
489 1
     * @return boolean
490
     */
491 1
    public function isRedirect(): bool
492
    {
493
        return $this->httpCode >= 300 && $this->httpCode < 400;
494
    }
495
496
    /**
497
     * Check is request has a 4XX HTTP code
498
     *
499 1
     * @return boolean
500
     */
501 1
    public function isClientError(): bool
502
    {
503
        return $this->httpCode >= 400 && $this->httpCode < 500;
504
    }
505
506
    /**
507
     * Check if request has a 5XX HTTP code
508
     *
509 1
     * @return boolean
510
     */
511 1
    public function isServerError(): bool
512
    {
513
        return $this->httpCode >= 500 && $this->httpCode < 600;
514
    }
515
516
    /**
517
     * Get string correspondig to HTTP code
518
     *
519 1
     * @return string|null
520
     */
521 1
    private function getStringForHttpCode(): ?string
522 1
    {
523 1
        if (isset($this->httpCodeString[$this->httpCode])) {
524 1
            return $this->httpCode .
525
                ' ' .
526
                $this->httpCodeString[$this->httpCode];
527 1
        }
528
529
        return null;
530
    }
531
}
532