Passed
Push — master ( ca348b...1f6505 )
by Marcio
03:43
created

RouterRequest::sendResponse()   D

Complexity

Conditions 18
Paths 112

Size

Total Lines 73
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 42
c 1
b 0
f 1
dl 0
loc 73
rs 4.7666
cc 18
nc 112
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 *
4
 * KNUT7 K7F (https://marciozebedeu.com/)
5
 * KNUT7 K7F (tm) : Rapid Development Framework (https://marciozebedeu.com/)
6
 *
7
 * Licensed under The MIT License
8
 * For full copyright and license information, please see the LICENSE.txt
9
 * Redistributions of files must retain the above copyright notice.
10
 *
11
 * @link      https://github.com/knut7/framework/ for the canonical source repository
12
 * @copyright (c) 2015.  KNUT7  Software Technologies AO Inc. (https://marciozebedeu.com/)
13
 * @license   https://marciozebedeu.com/license/new-bsd New BSD License
14
 * @author    Marcio Zebedeu - [email protected]
15
 * @version   1.0.14
16
 *
17
 *
18
 */
19
20
namespace Ballybran\Routing\Router;
21
22
use Ballybran\Core\Http\Request;
23
use Ballybran\Core\Http\Response;
24
25
class RouterRequest
26
{
27
    /**
28
     * @var string $validMethods Valid methods for Requests
29
     */
30
    public $validMethods = 'GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH|ANY|AJAX|XPOST|XPUT|XDELETE|XPATCH';
31
32
    /**
33
     * Request method validation
34
     *
35
     * @param string $data
36
     * @param string $method
37
     *
38
     * @return bool
39
     */
40
    public function validMethod($data, $method)
41
    {
42
        $valid = false;
43
        if (strstr($data, '|')) {
44
            foreach (explode('|', $data) as $value) {
45
                $valid = $this->checkMethods($value, $method);
46
                if ($valid) {
47
                    break;
48
                }
49
            }
50
        } else {
51
            $valid = $this->checkMethods($data, $method);
52
        }
53
54
        return $valid;
55
    }
56
57
    /**
58
     * Get the request method used, taking overrides into account
59
     *
60
     * @return string
61
     */
62
    public function getRequestMethod()
63
    {
64
        // Take the method as found in $_SERVER
65
        $method = $_SERVER['REQUEST_METHOD'];
66
        // If it's a HEAD request override it to being GET and prevent any output, as per HTTP Specification
67
        // @url http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
68
        if ($method === 'HEAD') {
69
            ob_start();
70
            $method = 'GET';
71
        } elseif ($method === 'POST') {
72
            $headers = $this->getRequestHeaders();
73
            if (isset($headers['X-HTTP-Method-Override']) &&
74
                in_array($headers['X-HTTP-Method-Override'], ['PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'])) {
75
                $method = $headers['X-HTTP-Method-Override'];
76
            } elseif (!empty($_POST['_method'])) {
77
                $method = strtoupper($_POST['_method']);
78
            }
79
        }
80
81
        return $method;
82
    }
83
84
    /**
85
     * check method valid
86
     *
87
     * @param string $value
88
     * @param string $method
89
     *
90
     * @return bool
91
     */
92
    protected function checkMethods($value, $method)
93
    {
94
        if (in_array($value, explode('|', $this->validMethods))) {
95
            if ($this->isAjax() && $value === 'AJAX') {
96
                return true;
97
            }
98
99
            if ($this->isAjax() && strpos($value, 'X') === 0 && $method === ltrim($value, 'X')) {
100
                return true;
101
            }
102
103
            if (in_array($value, [$method, 'ANY'])) {
104
                return true;
105
            }
106
        }
107
108
        return false;
109
    }
110
111
    /**
112
     * Check ajax request
113
     *
114
     * @return bool
115
     */
116
    protected function isAjax()
117
    {
118
        return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
119
    }
120
121
    /**
122
     * Get all request headers
123
     *
124
     * @return array
125
     */
126
    protected function getRequestHeaders()
127
    {
128
        // If getallheaders() is available, use that
129
        if (function_exists('getallheaders')) {
130
            return getallheaders();
0 ignored issues
show
Bug Best Practice introduced by
The expression return getallheaders() could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
131
        }
132
133
        // Method getallheaders() not available: manually extract 'm
134
        $headers = [];
135
        foreach ($_SERVER as $name => $value) {
136
            if (substr($name, 0, 5) == 'HTTP_' || $name === 'CONTENT_TYPE' || $name === 'CONTENT_LENGTH') {
137
                $headerKey = str_replace(
138
                    [' ', 'Http'],
139
                    ['-', 'HTTP'],
140
                    ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))
141
                );
142
                $headers[$headerKey] = $value;
143
            }
144
        }
145
146
        return $headers;
147
    }
148
149
      /**
150
     * This static method will create a new Request object, based on the
151
     * current PHP request.
152
     */
153
    public static function getRequest(): Request
154
    {
155
        $serverArr = $_SERVER;
156
157
        if ('cli' === PHP_SAPI) {
158
            // If we're running off the CLI, we're going to set some default
159
            // settings.
160
            $serverArr['REQUEST_URI'] = $_SERVER['REQUEST_URI'] ?? '/';
161
            $serverArr['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD'] ?? 'CLI';
162
        }
163
164
        $r = self::createFromServerArray($serverArr);
165
        $r->setBody(fopen('php://input', 'r'));
0 ignored issues
show
Bug introduced by
It seems like fopen('php://input', 'r') can also be of type false; however, parameter $body of Ballybran\Core\Http\Message::setBody() does only seem to accept callable|resource|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
        $r->setBody(/** @scrutinizer ignore-type */ fopen('php://input', 'r'));
Loading history...
166
        $r->setPostData($_POST);
167
168
        return $r;
169
    }
170
171
    /**
172
     * Sends the HTTP response back to a HTTP client.
173
     *
174
     * This calls php's header() function and streams the body to php://output.
175
     */
176
    public static function sendResponse(Response $response)
177
    {
178
        header('HTTP/'.$response->getHttpVersion().' '.$response->getStatus().' '.$response->getStatusText());
179
        foreach ($response->getHeaders() as $key => $value) {
180
            foreach ($value as $k => $v) {
181
                if (0 === $k) {
182
                    header($key.': '.$v);
183
                } else {
184
                    header($key.': '.$v, false);
185
                }
186
            }
187
        }
188
189
        $body = $response->getBody();
190
        if (null === $body) {
0 ignored issues
show
introduced by
The condition null === $body is always false.
Loading history...
191
            return;
192
        }
193
194
        if (is_callable($body)) {
195
            $body();
196
197
            return;
198
        }
199
200
        $contentLength = $response->getHeader('Content-Length');
201
        if (null !== $contentLength) {
202
            $output = fopen('php://output', 'wb');
203
            if (is_resource($body) && 'stream' == get_resource_type($body)) {
204
                if (PHP_INT_SIZE > 4) {
205
                    // use the dedicated function on 64 Bit systems
206
                    // a workaround to make PHP more possible to use mmap based copy, see https://github.com/sabre-io/http/pull/119
207
                    $left = (int) $contentLength;
208
                    // copy with 4MiB chunks
209
                    $chunk_size = 4 * 1024 * 1024;
210
                    stream_set_chunk_size($output, $chunk_size);
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $fp of stream_set_chunk_size() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

210
                    stream_set_chunk_size(/** @scrutinizer ignore-type */ $output, $chunk_size);
Loading history...
211
                    // If this is a partial response, flush the beginning bytes until the first position that is a multiple of the page size.
212
                    $contentRange = $response->getHeader('Content-Range');
213
                    // Matching "Content-Range: bytes 1234-5678/7890"
214
                    if (null !== $contentRange && preg_match('/^bytes\s([0-9]+)-([0-9]+)\//i', $contentRange, $matches)) {
215
                        // 4kB should be the default page size on most architectures
216
                        $pageSize = 4096;
217
                        $offset = (int) $matches[1];
218
                        $delta = ($offset % $pageSize) > 0 ? ($pageSize - $offset % $pageSize) : 0;
219
                        if ($delta > 0) {
220
                            $left -= stream_copy_to_stream($body, $output, min($delta, $left));
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $dest of stream_copy_to_stream() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
                            $left -= stream_copy_to_stream($body, /** @scrutinizer ignore-type */ $output, min($delta, $left));
Loading history...
221
                        }
222
                    }
223
                    while ($left > 0) {
224
                        $copied = stream_copy_to_stream($body, $output, min($left, $chunk_size));
225
                        // stream_copy_to_stream($src, $dest, $maxLength) must return the number of bytes copied or false in case of failure
226
                        // But when the $maxLength is greater than the total number of bytes remaining in the stream,
227
                        // It returns the negative number of bytes copied
228
                        // So break the loop in such cases.
229
                        if ($copied <= 0) {
230
                            break;
231
                        }
232
                        $left -= $copied;
233
                    }
234
                } else {
235
                    // workaround for 32 Bit systems to avoid stream_copy_to_stream
236
                    while (!feof($body)) {
237
                        fwrite($output, fread($body, 8192));
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

237
                        fwrite(/** @scrutinizer ignore-type */ $output, fread($body, 8192));
Loading history...
238
                    }
239
                }
240
            } else {
241
                fwrite($output, $body, (int) $contentLength);
0 ignored issues
show
Bug introduced by
It seems like $body can also be of type callable and resource; however, parameter $string of fwrite() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

241
                fwrite($output, /** @scrutinizer ignore-type */ $body, (int) $contentLength);
Loading history...
242
            }
243
        } else {
244
            file_put_contents('php://output', $body);
245
        }
246
247
        if (is_resource($body)) {
248
            fclose($body);
249
        }
250
    }
251
252
    /**
253
     * This static method will create a new Request object, based on a PHP
254
     * $_SERVER array.
255
     *
256
     * REQUEST_URI and REQUEST_METHOD are required.
257
     */
258
    public static function createFromServerArray(array $serverArray): Request
259
    {
260
        $headers = [];
261
        $method = null;
262
        $url = null;
263
        $httpVersion = '1.1';
264
265
        $protocol = 'http';
266
        $hostName = 'localhost';
267
268
        foreach ($serverArray as $key => $value) {
269
            switch ($key) {
270
                case 'SERVER_PROTOCOL':
271
                    if ('HTTP/1.0' === $value) {
272
                        $httpVersion = '1.0';
273
                    } elseif ('HTTP/2.0' === $value) {
274
                        $httpVersion = '2.0';
275
                    }
276
                    break;
277
                case 'REQUEST_METHOD':
278
                    $method = $value;
279
                    break;
280
                case 'REQUEST_URI':
281
                    $url = $value;
282
                    break;
283
284
                // These sometimes show up without a HTTP_ prefix
285
                case 'CONTENT_TYPE':
286
                    $headers['Content-Type'] = $value;
287
                    break;
288
                case 'CONTENT_LENGTH':
289
                    $headers['Content-Length'] = $value;
290
                    break;
291
292
                // mod_php on apache will put credentials in these variables.
293
                // (fast)cgi does not usually do this, however.
294
                case 'PHP_AUTH_USER':
295
                    if (isset($serverArray['PHP_AUTH_PW'])) {
296
                        $headers['Authorization'] = 'Basic '.base64_encode($value.':'.$serverArray['PHP_AUTH_PW']);
297
                    }
298
                    break;
299
300
                // Similarly, mod_php may also screw around with digest auth.
301
                case 'PHP_AUTH_DIGEST':
302
                    $headers['Authorization'] = 'Digest '.$value;
303
                    break;
304
305
                // Apache may prefix the HTTP_AUTHORIZATION header with
306
                // REDIRECT_, if mod_rewrite was used.
307
                case 'REDIRECT_HTTP_AUTHORIZATION':
308
                    $headers['Authorization'] = $value;
309
                    break;
310
311
                case 'HTTP_HOST':
312
                    $hostName = $value;
313
                    $headers['Host'] = $value;
314
                    break;
315
316
                case 'HTTPS':
317
                    if (!empty($value) && 'off' !== $value) {
318
                        $protocol = 'https';
319
                    }
320
                    break;
321
322
                default:
323
                    if ('HTTP_' === substr($key, 0, 5)) {
324
                        // It's a HTTP header
325
326
                        // Normalizing it to be prettier
327
                        $header = strtolower(substr($key, 5));
328
329
                        // Transforming dashes into spaces, and uppercasing
330
                        // every first letter.
331
                        $header = ucwords(str_replace('_', ' ', $header));
332
333
                        // Turning spaces into dashes.
334
                        $header = str_replace(' ', '-', $header);
335
                        $headers[$header] = $value;
336
                    }
337
                    break;
338
            }
339
        }
340
341
        if (null === $url) {
342
            throw new \InvalidArgumentException('The _SERVER array must have a REQUEST_URI key');
343
        }
344
345
        if (null === $method) {
346
            throw new \InvalidArgumentException('The _SERVER array must have a REQUEST_METHOD key');
347
        }
348
        $r = new Request($method, $url, $headers);
349
        $r->setHttpVersion($httpVersion);
350
        $r->setRawServerData($serverArray);
351
        $r->setAbsoluteUrl($protocol.'://'.$hostName.$url);
352
353
        return $r;
354
    }
355
    
356
}
357