Completed
Push — master ( 800bf3...4eaca9 )
by Ivan
03:16
created

Request::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26

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 9.504
c 0
b 0
f 0
cc 1
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  = \Zend\Diactoros\normalizeServer($server ?: $_SERVER);
31
        $files   = \Zend\Diactoros\normalizeUploadedFiles($files ?: $_FILES);
32
        $headers = [];
33
        foreach ($server as $key => $value) {
34
            if (strpos($key, 'REDIRECT_') === 0) {
35
                $key = substr($key, 9);
36
                if (array_key_exists($key, $server)) {
37
                    continue;
38
                }
39
            }
40
            if (strlen($value) && strpos($key, 'HTTP_') === 0) {
41
                $name = strtr(strtolower(substr($key, 5)), '_', '-');
42
                $headers[$name] = $value;
43
                continue;
44
            }
45
            if (strlen($value) && strpos($key, 'CONTENT_') === 0) {
46
                $name = 'content-' . strtolower(substr($key, 8));
47
                $headers[$name] = $value;
48
                continue;
49
            }
50
        }
51
52
        $method  = \Zend\Diactoros\marshalMethodFromSapi($server);
53
        $uri     = \Zend\Diactoros\marshalUriFromSapi($server, $headers);
54
55
        if (null === $cookies && array_key_exists('cookie', $headers)) {
56
            $cookies = self::parseCookieHeader($headers['cookie']);
57
        }
58
        
59
60
        if ($body === null) {
61
            $body = [];
62
            if (isset($headers['content-type']) && strpos($headers['content-type'], 'json') !== false) {
63
                $body = json_decode($body, true);
64
                if ($body === null) {
65
                    $body = [];
66
                }
67
            } else {
68
                $body = static::fixedQueryParams(file_get_contents('php://input'));
69
            }
70
        }
71
72
        return new static(
73
            $server,
74
            $files,
75
            $uri,
76
            $method,
77
            'php://input',
78
            $headers,
79
            $cookies ?: $_COOKIE,
80
            $query ?: static::fixedQueryParams($uri->getQuery()),
81
            $body ?: (count($_POST) ? $_POST : json_decode(file_get_contents('php://input'), true)),
82
            \Zend\Diactoros\marshalProtocolVersionFromSapi($server)
83
        );
84
    }
85 1
    public static function fromString(string $str) : Request
86
    {
87 1
        $method = 'GET';
88 1
        $version = '1.1';
89 1
        $uri = '/';
90 1
        $headers = [];
91 1
        $files = [];
92 1
        $body = '';
93
94 1
        $break = strpos($str, "\r\n\r\n") === false ? "\n" : "\r\n"; // just in case someone breaks RFC 2616
95
96 1
        list($headers, $message) = array_pad(explode($break . $break, $str, 2), 2, '');
97 1
        $headers = explode($break, preg_replace("(" . $break . "\s+)", " ", $headers));
98 1
        if (isset($headers[0]) && strlen($headers[0])) {
99 1
            $temp = explode(' ', $headers[0]);
100 1
            if (in_array($temp[0], ['GET', 'POST', 'HEAD', 'PATCH', 'PUT', 'OPTIONS', 'TRACE', 'DELETE'])) {
101 1
                $method = $temp[0];
102 1
                $uri = $temp[1];
103 1
                if (isset($temp[2])) {
104 1
                    $version = substr($temp[2], 5);
105
                }
106 1
                unset($headers[0]);
107 1
                $headers = array_values($headers);
108
            }
109
        }
110 1
        $temp = array_filter($headers);
111 1
        $headers = [];
112 1
        foreach ($temp as $v) {
113 1
            $v = explode(':', $v, 2);
114 1
            $name = trim($v[0]);
115 1
            $name = str_replace('_', ' ', strtolower($name));
116 1
            $name = str_replace('-', ' ', strtolower($name));
117 1
            $name = str_replace(' ', '-', ucwords($name));
118 1
            $headers[$name] = trim($v[1]);
119
        }
120 1
        if (isset($headers['Host'])) {
121
            $uri = $headers['Host'] . $uri;
122
        } else {
123 1
            $uri = 'localhost' . $uri;
124
        }
125 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart') !== false) {
126
            $bndr = trim(explode(' boundary=', $headers['Content-Type'])[1], '"');
127
            $parts = explode($break . '--' . $bndr, $break . $message);
128
            if (count($parts) == 1) {
129
                $body = $message;
130
            } else {
131
                array_pop($parts);
132
                array_shift($parts);
133
                $post = [];
134
                $fres = [];
135
                foreach ($parts as $k => $item) {
136
                    list($head, $pbody) = explode($break . $break, $item, 2);
137
                    $head = explode($break, preg_replace("(" . $break . "\s+)", " ", $head));
138
                    foreach ($head as $h) {
139
                        if (strpos(strtolower($h), 'content-disposition') === 0) {
140
                            $cd = explode(';', $h);
141
                            $name = '';
142
                            $file = '';
143
                            foreach ($cd as $p) {
144
                                if (strpos(trim($p), 'name=') === 0) {
145
                                    $name = trim(explode('name=', $p)[1], ' "');
146
                                }
147
                                if (strpos(trim($p), 'filename=') === 0) {
148
                                    $file = trim(explode('filename=', $p)[1], ' "');
149
                                }
150
                            }
151
                            if ($file) {
152
                                // create resource manually
153
                                $fres[$k] = fopen('php://temp', 'wb+');
154
                                fwrite($fres[$k], $pbody);
155
                                rewind($fres[$k]);
156
                                $files[$name] = new UploadedFile(
157
                                    $fres[$k],
158
                                    strlen($pbody),
159
                                    UPLOAD_ERR_OK,
160
                                    $file
161
                                );
162
                            } else {
163
                                $post[$name] = $pbody;
164
                            }
165
                        }
166
                    }
167
                }
168
                $body = http_build_query($post);
169
            }
170 1
        } elseif (strlen($message)) {
171
            $body = $message;
172
        }
173 1
        if (strpos($uri, '://') === false) {
174 1
            $uri = 'http://' . $uri;
175
        }
176
177 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'json') !== false) {
178
            $params = json_decode($body, true);
179
        } else {
180 1
            $params = static::fixedQueryParams($body);
181
        }
182 1
        $temp = (new Stream('php://temp', 'wb+'));
183 1
        $temp->write($body);
184 1
        $uri = new ZendUri($uri);
185 1
        return new static(
186 1
            [],
187 1
            \Zend\Diactoros\normalizeUploadedFiles($files),
188 1
            $uri,
189 1
            $method,
190 1
            $temp,
191 1
            $headers,
192 1
            isset($headers['Cookie']) ? self::parseCookieHeader($headers['Cookie']) : [],
193 1
            static::fixedQueryParams($uri->getQuery()),
194 1
            $params ?? [],
195 1
            $version
196
        );
197
    }
198 2
    public static function fixedQueryParams($query)
199
    {
200 2
        $data = [];
201 2
        $temp = strlen($query) ? explode('&', $query) : [];
202 2
        foreach ($temp as $var) {
203 2
            $var   = explode('=', $var, 2);
204 2
            $name  = urldecode($var[0]);
205 2
            $value = isset($var[1]) ? urldecode($var[1]) : '';
206 2
            $name  = explode(']', str_replace(['][', '['], ']', $name));
207 2
            $name  = count($name) > 1 ? array_slice($name, 0, -1) : $name;
208
209 2
            $tmp = &$data;
210 2
            foreach ($name as $k) {
211 2
                if ($k === "") {
212
                    continue;
213
                }
214 2
                if (!isset($tmp[$k])) {
215 2
                    $tmp[$k] = [];
216
                }
217 2
                $tmp = &$tmp[$k];
218
            }
219 2
            if ($name[count($name) - 1] == '') {
220
                $tmp[] = $value;
221
            } else {
222 2
                $tmp = $value;
223
            }
224
        }
225 2
        return $data;
226
    }
227 1
    private static function parseCookieHeader($cookieHeader)
228
    {
229 1
        preg_match_all('(
230
            (?:^\\n?[ \t]*|;[ ])
231
            (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
232
            =
233
            (?P<DQUOTE>"?)
234
                (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
235
            (?P=DQUOTE)
236
            (?=\\n?[ \t]*$|;[ ])
237 1
        )x', $cookieHeader, $matches, PREG_SET_ORDER);
238
239 1
        $cookies = [];
240
241 1
        if (is_array($matches)) {
242 1
            foreach ($matches as $match) {
243 1
                $cookies[$match['name']] = urldecode($match['value']);
244
            }
245
        }
246
247 1
        return $cookies;
248
    }
249 1
    public function __construct(
250
        array $serverParams = [],
251
        array $uploadedFiles = [],
252
        $uri = null,
253
        $method = null,
254
        $body = 'php://input',
255
        array $headers = [],
256
        array $cookies = [],
257
        array $queryParams = [],
258
        $parsedBody = null,
259
        $protocol = '1.1'
260
    ) {
261 1
        $uri = new Uri((string)$uri);
262 1
        parent::__construct(
263 1
            $serverParams,
264 1
            $uploadedFiles,
265 1
            $uri,
266 1
            $method,
267 1
            $body,
268 1
            $headers,
269 1
            $cookies,
270 1
            $queryParams,
271 1
            $parsedBody,
272 1
            $protocol
273
        );
274 1
    }
275 1
    protected function cleanValue($value, $mode = null)
276
    {
277 1
        if (is_array($value)) {
278
            $temp = [];
279
            foreach ($value as $k => $v) {
280
                $temp[$k] = $this->cleanValue($v, $mode);
281
            }
282
            return $temp;
283
        }
284
        // normalize newlines
285 1
        if (strpos((string)$value, "\r") !== false) {
286
            $value = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $value);
287
        }
288
        // remove invalid utf8 chars
289 1
        if (preg_match('/[^\x00-\x7F]/S', $value) != 0) {
290
            $temp = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
291
            if ($temp !== false) {
292
                $value = $temp;
293
            }
294
        }
295
        // remove non-printable chars
296
        do {
297 1
            $count = 0;
298 1
            $value = preg_replace(['/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'], '', $value, -1, $count);
299 1
        } while ((int)$count > 0);
300
301
        switch ($mode) {
302 1
            case 'int':
303 1
                $value = (int) $value;
304 1
                break;
305 1
            case 'float':
306
                $value = (float) $value;
307
                break;
308 1
            case 'nohtml':
309
                $value = strip_tags((string) $value);
310
                break;
311 1
            case 'escape':
312
                $value = htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
313
                break;
314 1
            case 'string':
315
                $value = (string) $value;
316
                break;
317 1
            case 'raw':
318
            default:
319 1
                break;
320
        }
321
322 1
        return $value;
323
    }
324 1
    protected function getValue(array $collection, $key, $default, $mode)
325
    {
326 1
        if ($key === null) {
327
            return $this->cleanValue($collection, $mode);
328
        }
329 1
        return isset($collection[$key]) ? $this->cleanValue($collection[$key], $mode) : $default;
330
    }
331
    /**
332
     * Gets a value from a cookie that came with the request
333
     * @param  string    $key     the cookie name
334
     * @param  mixed     $default optional default value to return if the key is not present (default to `null`)
335
     * @param  string    $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
336
     * @return mixed             the value (or values)
337
     */
338 1
    public function getCookie($key = null, $default = null, $mode = null)
339
    {
340 1
        return $this->getValue($this->getCookieParams(), $key, $default, $mode);
341
    }
342
    /**
343
     * Get a GET param from the request URL
344
     * @param  string   $key     the GET param name
345
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
346
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
347
     * @return mixed             the value (or values)
348
     */
349 1
    public function getQuery($key = null, $default = null, $mode = null)
350
    {
351 1
        return $this->getValue($this->getQueryParams(), $key, $default, $mode);
352
    }
353
    /**
354
     * Get a param from the request body (if it is in JSON format it will be parsed out as well)
355
     * @param  string   $key     the param name
356
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
357
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
358
     * @return mixed             the value (or values if no key was specified)
359
     */
360
    public function getPost($key = null, $default = null, $mode = null)
361
    {
362
        $body = $this->getParsedBody();
363
        if (!is_array($body)) {
364
            $body = [];
365
        }
366
        return $this->getValue($body, $key, $default, $mode);
367
    }
368
    /**
369
     * Get any authorization details supplied with the request.
370
     * @return array|null           array of extracted values or null (possible keys are username, password and token)
371
     */
372
    public function getAuthorization()
373
    {
374
        if (!$this->hasHeader('Authorization')) {
375
            return null;
376
        }
377
        $temp = explode(' ', trim($this->getHeaderLine('Authorization')), 2);
378
        switch (strtolower($temp[0])) {
379
            case 'basic':
380
                $temp[1] = base64_decode($temp[1]);
381
                $temp[1] = explode(':', $temp[1], 2);
382
                return ['username' => $temp[1][0], 'password' => $temp[1][1] ?? null];
383
            case 'token':
384
            case 'oauth':
385
            case 'bearer':
386
                return ['token' => $temp[1] ?? null];
387
            default:
388
                return null;
389
        }
390
    }
391
    public function getUrl()
392
    {
393
        return $this->getUri();
394
    }
395
    /**
396
     * Determine if this is an AJAX request
397
     * @return boolean is the request AJAX
398
     */
399 1
    public function isAjax()
400
    {
401 1
        return ($this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest');
402
    }
403
    /**
404
     * Determine if this is an CORS request
405
     * @return boolean is the request CORS
406
     */
407 1
    public function isCors()
408
    {
409 1
        if (!$this->hasHeader('Origin')) {
410 1
            return false;
411
        }
412
        $origin = parse_url($this->getHeaderLine('Origin'));
413
        $host   = $this->getUri()->getHost();
414
        $scheme = $this->getUri()->getScheme();
415
        return (
416
            !$host ||
417
            strtolower($origin['scheme']?? '') !== strtolower($scheme) ||
418
            strpos(strtolower($origin['host'] ?? ''), strtolower($host)) === false
419
        );
420
    }
421
    /**
422
     * Get the prefered response languages (parses the Accept-Language header if present).
423
     * @param  bool    $shortNames should values like "en-US", be truncated to "en", defaults to true
424
     * @return array   array of ordered lowercase language codes
425
     */
426 1
    public function getPreferredResponseLanguages(bool $shortNames = true) : array
427
    {
428 1
        $acpt = $this->getHeaderLine('Accept-Language') ?: '*';
429 1
        $acpt = explode(',', $acpt);
430 1
        foreach ($acpt as $k => $v) {
431 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
432 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
433 1
            $v[0] = $shortNames ? explode('-', $v[0], 2)[0] : $v[0];
434 1
            $v[2] = $k;
435 1
            $acpt[$k] = $v;
436
        }
437
        usort($acpt, function ($a, $b) {
438 1
            if ($a[1] > $b[1]) {
439 1
                return -1;
440
            }
441
            if ($a[1] < $b[1]) {
442
                return 1;
443
            }
444
            return $a[2] < $b[2] ? -1 : 1;
445 1
        });
446
        $acpt = array_map(function ($v) {
447 1
            return strtolower($v[0]);
448 1
        }, $acpt);
449
        $acpt = array_filter($acpt, function ($v) {
450 1
            return $v !== '*';
451 1
        });
452 1
        return array_unique($acpt);
453
    }
454
    /**
455
     * Get the preffered response language (parses the Accept-Language header if present).
456
     * @param  string       $default the default code to return if the header is not found
457
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
458
     * @return string       the prefered language code
459
     */
460 1
    public function getPreferredResponseLanguage(string $default = 'en', array $allowed = null) : string
461
    {
462 1
        $acpt = $this->getPreferredResponseLanguages(true);
463 1
        foreach ($acpt as $lang) {
464 1
            if ($allowed === null) {
465 1
                return $lang;
466
            }
467
            if (in_array($lang, $allowed)) {
468
                return $lang;
469
            }
470
        }
471 1
        return $default;
472
    }
473
    /**
474
     * Get the prefered response formats.
475
     * @param  string                    $default the default value to return if the Accept header is not present.
476
     * @return string[]                  the desired response formats
477
     */
478 1
    public function getPreferredResponseFormats($default = 'text/html')
479
    {
480
        // parse accept header (uses default instead of 406 header)
481 1
        $acpt = $this->getHeaderLine('Accept') ?: $default;
482 1
        $acpt = explode(',', $acpt);
483 1
        foreach ($acpt as $k => $v) {
484 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
485 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
486 1
            $v[0] = $v[0];
487 1
            $v[2] = $k;
488 1
            $acpt[$k] = $v;
489
        }
490
        usort($acpt, function ($a, $b) {
491 1
            if ($a[1] > $b[1]) {
492 1
                return -1;
493
            }
494 1
            if ($a[1] < $b[1]) {
495
                return 1;
496
            }
497 1
            return $a[2] < $b[2] ? -1 : 1;
498 1
        });
499
        $acpt = array_map(function ($v) {
500 1
            return strtolower($v[0]);
501 1
        }, $acpt);
502 1
        $acpt = array_filter($acpt, function ($v) {
503 1
            return $v !== '*/*';
504 1
        });
505 1
        return array_unique($acpt);
506
    }
507
    /**
508
     * Get the preffered response language (parses the Accept-Language header if present).
509
     * @param  string       $default the default code to return if the header is not found
510
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
511
     * @return string       the prefered language code
512
     */
513 1
    public function getPreferredResponseFormat(string $default = 'text/html', array $allowed = null) : string
514
    {
515
        // parse accept header (uses default instead of 406 header)
516 1
        $acpt = $this->getPreferredResponseFormats();
517 1
        foreach ($acpt as $format) {
518 1
            if ($allowed === null) {
519 1
                return $format;
520
            }
521
            if (in_array($format, $allowed)) {
522
                return $format;
523
            }
524
        }
525
        return $default;
526
    }
527
}
528