Completed
Push — master ( 201321...f974f3 )
by Ivan
01:52
created

Request::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 14
cts 14
cp 1
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 23
nc 1
nop 10
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace vakata\http;
4
5
use Zend\Diactoros\Uri as ZendUri;
6
use Zend\Diactoros\Stream;
7
use Zend\Diactoros\UploadedFile;
8
use Zend\Diactoros\ServerRequest;
9
use Zend\Diactoros\ServerRequestFactory;
10
11
class Request extends ServerRequest
12
{
13
    /**
14
     * Create an instance from globals
15
     *
16
     * @param array $server
17
     * @param array $query
18
     * @param array $body
19
     * @param array $cookies
20
     * @param array $files
21
     * @return Request
22
     */
23
    public static function fromGlobals(
24
        array $server = null,
25
        array $query = null,
26
        array $body = null,
27
        array $cookies = null,
28
        array $files = null
29
    ) {
30
        $server  = ServerRequestFactory::normalizeServer($server ?: $_SERVER);
31
        $files   = ServerRequestFactory::normalizeFiles($files ?: $_FILES);
32
        $headers = ServerRequestFactory::marshalHeaders($server);
33
34
        if (null === $cookies && array_key_exists('cookie', $headers)) {
35
            $cookies = self::parseCookieHeader($headers['cookie']);
36
        }
37
        $uri = ServerRequestFactory::marshalUriFromServer($server, $headers);
38
39
        if ($body === null) {
40
            $body = [];
41
            if (isset($headers['content-type']) && strpos($headers['content-type'], 'json') !== false) {
42
                $body = json_decode($body, true);
43
                if ($body === null) {
44
                    $body = [];
45
                }
46
            } else {
47
                $body = static::fixedQueryParams(file_get_contents('php://input'));
48
            }
49
        }
50
51
        return new static(
52
            $server,
53
            $files,
54
            $uri,
55
            ServerRequestFactory::get('REQUEST_METHOD', $server, 'GET'),
56
            'php://input',
57
            $headers,
58
            $cookies ?: $_COOKIE,
59
            $query ?: static::fixedQueryParams($uri->getQuery()),
60
            $body ?: (count($_POST) ? $_POST : json_decode(file_get_contents('php://input'), true)),
61
            self::marshalProtocolVersion($server)
62
        );
63
    }
64 1
    public static function fromString(string $str) : Request
65
    {
66 1
        $method = 'GET';
67 1
        $version = '1.1';
68 1
        $uri = '/';
69 1
        $headers = [];
70 1
        $files = [];
71 1
        $body = '';
72
73 1
        $break = strpos($str, "\r\n\r\n") === false ? "\n" : "\r\n"; // just in case someone breaks RFC 2616
74
75 1
        list($headers, $message) = array_pad(explode($break . $break, $str, 2), 2, '');
76 1
        $headers = explode($break, preg_replace("(" . $break . "\s+)", " ", $headers));
77 1
        if (isset($headers[0]) && strlen($headers[0])) {
78 1
            $temp = explode(' ', $headers[0]);
79 1
            if (in_array($temp[0], ['GET', 'POST', 'HEAD', 'PATCH', 'PUT', 'OPTIONS', 'TRACE', 'DELETE'])) {
80 1
                $method = $temp[0];
81 1
                $uri = $temp[1];
82 1
                if (isset($temp[2])) {
83 1
                    $version = substr($temp[2], 5);
84
                }
85 1
                unset($headers[0]);
86 1
                $headers = array_values($headers);
87
            }
88
        }
89 1
        $temp = array_filter($headers);
90 1
        $headers = [];
91 1
        foreach ($temp as $v) {
92 1
            $v = explode(':', $v, 2);
93 1
            $name = trim($v[0]);
94 1
            $name = str_replace('_', ' ', strtolower($name));
95 1
            $name = str_replace('-', ' ', strtolower($name));
96 1
            $name = str_replace(' ', '-', ucwords($name));
97 1
            $headers[$name] = trim($v[1]);
98
        }
99 1
        if (isset($headers['Host'])) {
100
            $uri = $headers['Host'] . $uri;
101
        } else {
102 1
            $uri = 'localhost' . $uri;
103
        }
104 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart') !== false) {
105
            $bndr = trim(explode(' boundary=', $headers['Content-Type'])[1], '"');
106
            $parts = explode($break . '--' . $bndr, $break . $message);
107
            if (count($parts) == 1) {
108
                $body = $message;
109
            } else {
110
                array_pop($parts);
111
                array_shift($parts);
112
                $post = [];
113
                $fres = [];
114
                foreach ($parts as $k => $item) {
115
                    list($head, $pbody) = explode($break . $break, $item, 2);
116
                    $head = explode($break, preg_replace("(" . $break . "\s+)", " ", $head));
117
                    foreach ($head as $h) {
118
                        if (strpos(strtolower($h), 'content-disposition') === 0) {
119
                            $cd = explode(';', $h);
120
                            $name = '';
121
                            $file = '';
122
                            foreach ($cd as $p) {
123
                                if (strpos(trim($p), 'name=') === 0) {
124
                                    $name = trim(explode('name=', $p)[1], ' "');
125
                                }
126
                                if (strpos(trim($p), 'filename=') === 0) {
127
                                    $file = trim(explode('filename=', $p)[1], ' "');
128
                                }
129
                            }
130
                            if ($file) {
131
                                // create resource manually
132
                                $fres[$k] = fopen('php://temp', 'wb+');
133
                                fwrite($fres[$k], $pbody);
134
                                rewind($fres[$k]);
135
                                $files[$name] = new UploadedFile(
136
                                    $fres[$k],
137
                                    strlen($pbody),
138
                                    UPLOAD_ERR_OK,
139
                                    $file
140
                                );
141
                            } else {
142
                                $post[$name] = $pbody;
143
                            }
144
                        }
145
                    }
146
                }
147
                $body = http_build_query($post);
148
            }
149 1
        } elseif (strlen($message)) {
150
            $body = $message;
151
        }
152 1
        if (strpos($uri, '://') === false) {
153 1
            $uri = 'http://' . $uri;
154
        }
155
156 1
        $params = [];
0 ignored issues
show
Unused Code introduced by
$params is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
157 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'json') !== false) {
158
            $params = json_decode($body, true);
159
        } else {
160 1
            $params = static::fixedQueryParams($body);
161
        }
162 1
        $temp = (new Stream('php://temp', 'wb+'));
163 1
        $temp->write($body);
164 1
        $uri = new ZendUri($uri);
165 1
        return new static(
166 1
            [],
167 1
            ServerRequestFactory::normalizeFiles($files),
168 1
            $uri,
169 1
            $method,
170 1
            $temp,
171 1
            $headers,
172 1
            isset($headers['Cookie']) ? self::parseCookieHeader($headers['Cookie']) : [],
173 1
            static::fixedQueryParams($uri->getQuery()),
174 1
            $params ?? [],
175 1
            $version
176
        );
177
    }
178 2
    public static function fixedQueryParams($query)
179
    {
180 2
        $data = [];
181 2
        $temp = strlen($query) ? explode('&', $query) : [];
182 2
        foreach ($temp as $var) {
183 2
            $var   = explode('=', $var, 2);
184 2
            $name  = urldecode($var[0]);
185 2
            $value = isset($var[1]) ? urldecode($var[1]) : '';
186 2
            $name  = explode(']', str_replace(['][', '['], ']', $name));
187 2
            $name  = count($name) > 1 ? array_slice($name, 0, -1) : $name;
188
189 2
            $tmp = &$data;
190 2
            foreach ($name as $k) {
191 2
                if ($k === "") {
192
                    continue;
193
                }
194 2
                if (!isset($tmp[$k])) {
195 2
                    $tmp[$k] = [];
196
                }
197 2
                $tmp = &$tmp[$k];
198
            }
199 2
            if ($name[count($name) - 1] == '') {
200
                $tmp[] = $value;
201
            } else {
202 2
                $tmp = $value;
203
            }
204
        }
205 2
        return $data;
206
    }
207 1
    private static function parseCookieHeader($cookieHeader)
208
    {
209 1
        preg_match_all('(
210
            (?:^\\n?[ \t]*|;[ ])
211
            (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
212
            =
213
            (?P<DQUOTE>"?)
214
                (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
215
            (?P=DQUOTE)
216
            (?=\\n?[ \t]*$|;[ ])
217 1
        )x', $cookieHeader, $matches, PREG_SET_ORDER);
218
219 1
        $cookies = [];
220
221 1
        if (is_array($matches)) {
222 1
            foreach ($matches as $match) {
223 1
                $cookies[$match['name']] = urldecode($match['value']);
224
            }
225
        }
226
227 1
        return $cookies;
228
    }
229
    private static function marshalProtocolVersion(array $server)
230
    {
231
        if (! isset($server['SERVER_PROTOCOL'])) {
232
            return '1.1';
233
        }
234
235
        if (! preg_match('#^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$#', $server['SERVER_PROTOCOL'], $matches)) {
236
            throw new \Exception(sprintf(
237
                'Unrecognized protocol version (%s)',
238
                $server['SERVER_PROTOCOL']
239
            ));
240
        }
241
242
        return $matches['version'];
243
    }
244 1
    public function __construct(
245
        array $serverParams = [],
246
        array $uploadedFiles = [],
247
        $uri = null,
248
        $method = null,
249
        $body = 'php://input',
250
        array $headers = [],
251
        array $cookies = [],
252
        array $queryParams = [],
253
        $parsedBody = null,
254
        $protocol = '1.1'
255
    ) {
256 1
        $uri = new Uri((string)$uri);
257 1
        parent::__construct(
258 1
            $serverParams,
259 1
            $uploadedFiles,
260 1
            $uri,
261 1
            $method,
262 1
            $body,
263 1
            $headers,
264 1
            $cookies,
265 1
            $queryParams,
266 1
            $parsedBody,
267 1
            $protocol
268
        );
269 1
    }
270 1
    protected function cleanValue($value, $mode = null)
271
    {
272 1
        if (is_array($value)) {
273
            $temp = [];
274
            foreach ($value as $k => $v) {
275
                $temp[$k] = $this->cleanValue($v, $mode);
276
            }
277
            return $temp;
278
        }
279
        // normalize newlines
280 1
        if (strpos((string)$value, "\r") !== false) {
281
            $value = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $value);
282
        }
283
        // remove invalid utf8 chars
284 1
        if (preg_match('/[^\x00-\x7F]/S', $value) != 0) {
285
            $temp = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
286
            if ($temp !== false) {
287
                $value = $temp;
288
            }
289
        }
290
        // remove non-printable chars
291
        do {
292 1
            $count = 0;
293 1
            $value = preg_replace(['/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'], '', $value, -1, $count);
294 1
        } while ((int)$count > 0);
295
296
        switch ($mode) {
297 1
            case 'int':
298 1
                $value = (int) $value;
299 1
                break;
300 1
            case 'float':
301
                $value = (float) $value;
302
                break;
303 1
            case 'nohtml':
304
                $value = strip_tags((string) $value);
305
                break;
306 1
            case 'escape':
307
                $value = htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
308
                break;
309 1
            case 'string':
310
                $value = (string) $value;
311
                break;
312 1
            case 'raw':
313
            default:
314 1
                break;
315
        }
316
317 1
        return $value;
318
    }
319 1
    protected function getValue(array $collection, $key, $default, $mode)
320
    {
321 1
        if ($key === null) {
322
            return $this->cleanValue($collection, $mode);
323
        }
324 1
        return isset($collection[$key]) ? $this->cleanValue($collection[$key], $mode) : $default;
325
    }
326
    /**
327
     * Gets a value from a cookie that came with the request
328
     * @param  string    $key     the cookie name
329
     * @param  mixed     $default optional default value to return if the key is not present (default to `null`)
330
     * @param  string    $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
331
     * @return string|array             the value (or values)
332
     */
333 1
    public function getCookie($key = null, $default = null, $mode = null)
334
    {
335 1
        return $this->getValue($this->getCookieParams(), $key, $default, $mode);
336
    }
337
    /**
338
     * Get a GET param from the request URL
339
     * @param  string   $key     the GET param name
340
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
341
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
342
     * @return string|array             the value (or values)
343
     */
344 1
    public function getQuery($key = null, $default = null, $mode = null)
345
    {
346 1
        return $this->getValue($this->getQueryParams(), $key, $default, $mode);
347
    }
348
    /**
349
     * Get a param from the request body (if it is in JSON format it will be parsed out as well)
350
     * @param  string   $key     the param name
351
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
352
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
353
     * @return string|array             the value (or values if no key was specified)
354
     */
355
    public function getPost($key = null, $default = null, $mode = null)
356
    {
357
        $body = $this->getParsedBody();
358
        if (!is_array($body)) {
359
            $body = [];
360
        }
361
        return $this->getValue($body, $key, $default, $mode);
362
    }
363
    /**
364
     * Get any authorization details supplied with the request.
365
     * @return array|null           array of extracted values or null (possible keys are username, password and token)
366
     */
367
    public function getAuthorization()
368
    {
369
        if (!$this->hasHeader('Authorization')) {
370
            return null;
371
        }
372
        $temp = explode(' ', trim($this->getHeaderLine('Authorization')), 2);
373
        switch (strtolower($temp[0])) {
374
            case 'basic':
375
                $temp[1] = base64_decode($temp[1]);
376
                $temp[1] = explode(':', $temp[1], 2);
377
                return ['username' => $temp[1][0], 'password' => $temp[1][1] ?? null];
378
            case 'token':
379
            case 'oauth':
380
            case 'bearer':
381
                return ['token' => $temp[1] ?? null];
382
            default:
383
                return null;
384
        }
385
    }
386
    public function getUrl()
387
    {
388
        return $this->getUri();
389
    }
390
    /**
391
     * Determine if this is an AJAX request
392
     * @return boolean is the request AJAX
393
     */
394 1
    public function isAjax()
395
    {
396 1
        return ($this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest');
397
    }
398
    /**
399
     * Determine if this is an CORS request
400
     * @return boolean is the request CORS
401
     */
402 1
    public function isCors()
403
    {
404 1
        if (!$this->hasHeader('Origin')) {
405 1
            return false;
406
        }
407
        $origin = parse_url($this->getHeaderLine('Origin'));
408
        $host   = $this->getUri()->getHost();
409
        $scheme = $this->getUri()->getScheme();
410
        return (
411
            !$host ||
412
            strtolower($origin['scheme']?? '') !== strtolower($scheme) ||
413
            strpos(strtolower($origin['host'] ?? ''), strtolower($host)) === false
414
        );
415
    }
416
    /**
417
     * Get the prefered response languages (parses the Accept-Language header if present).
418
     * @param  bool    $shortNames should values like "en-US", be truncated to "en", defaults to true
419
     * @return array   array of ordered lowercase language codes
420
     */
421 1
    public function getPreferredResponseLanguages(bool $shortNames = true) : array
422
    {
423 1
        $acpt = $this->getHeaderLine('Accept-Language') ?: '*';
424 1
        $acpt = explode(',', $acpt);
425 1
        foreach ($acpt as $k => $v) {
426 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
427 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
428 1
            $v[0] = $shortNames ? explode('-', $v[0], 2)[0] : $v[0];
429 1
            $v[2] = $k;
430 1
            $acpt[$k] = $v;
431
        }
432
        usort($acpt, function ($a, $b) {
433 1
            if ($a[1] > $b[1]) {
434 1
                return -1;
435
            }
436
            if ($a[1] < $b[1]) {
437
                return 1;
438
            }
439
            return $a[2] < $b[2] ? -1 : 1;
440 1
        });
441
        $acpt = array_map(function ($v) {
442 1
            return strtolower($v[0]);
443 1
        }, $acpt);
444
        $acpt = array_filter($acpt, function ($v) {
445 1
            return $v !== '*';
446 1
        });
447 1
        return array_unique($acpt);
448
    }
449
    /**
450
     * Get the preffered response language (parses the Accept-Language header if present).
451
     * @param  string       $default the default code to return if the header is not found
452
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
453
     * @return string       the prefered language code
454
     */
455 1
    public function getPreferredResponseLanguage(string $default = 'en', array $allowed = null) : string
456
    {
457 1
        $acpt = $this->getPreferredResponseLanguages(true);
458 1
        foreach ($acpt as $lang) {
459 1
            if ($allowed === null) {
460 1
                return $lang;
461
            }
462
            if (in_array($lang, $allowed)) {
463
                return $lang;
464
            }
465
        }
466 1
        return $default;
467
    }
468
    /**
469
     * Get the prefered response formats.
470
     * @param  string                    $default the default value to return if the Accept header is not present.
471
     * @return string[]                  the desired response formats
472
     */
473 1
    public function getPreferredResponseFormats($default = 'text/html')
474
    {
475
        // parse accept header (uses default instead of 406 header)
476 1
        $acpt = $this->getHeaderLine('Accept') ?: $default;
477 1
        $acpt = explode(',', $acpt);
478 1
        foreach ($acpt as $k => $v) {
479 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
480 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
481 1
            $v[0] = $v[0];
482 1
            $v[2] = $k;
483 1
            $acpt[$k] = $v;
484
        }
485
        usort($acpt, function ($a, $b) {
486 1
            if ($a[1] > $b[1]) {
487 1
                return -1;
488
            }
489 1
            if ($a[1] < $b[1]) {
490
                return 1;
491
            }
492 1
            return $a[2] < $b[2] ? -1 : 1;
493 1
        });
494
        $acpt = array_map(function ($v) {
495 1
            return strtolower($v[0]);
496 1
        }, $acpt);
497 1
        $acpt = array_filter($acpt, function ($v) {
498 1
            return $v !== '*/*';
499 1
        });
500 1
        return array_unique($acpt);
501
    }
502
    /**
503
     * Get the preffered response language (parses the Accept-Language header if present).
504
     * @param  string       $default the default code to return if the header is not found
505
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
506
     * @return string       the prefered language code
507
     */
508 1
    public function getPreferredResponseFormat(string $default = 'text/html', array $allowed = null) : string
509
    {
510
        // parse accept header (uses default instead of 406 header)
511 1
        $acpt = $this->getPreferredResponseFormats();
512 1
        foreach ($acpt as $format) {
513 1
            if ($allowed === null) {
514 1
                return $format;
515
            }
516
            if (in_array($format, $allowed)) {
517
                return $format;
518
            }
519
        }
520
        return $default;
521
    }
522
}
523