Completed
Pull Request — master (#234)
by Дмитрий
07:51
created

Generic::checkIfReady()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 8
dl 0
loc 22
rs 6.6037
eloc 14
c 2
b 1
f 0
nc 6
nop 0
1
<?php
2
namespace PHPDaemon\HTTPRequest;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\FS\File;
7
use PHPDaemon\FS\FileSystem;
8
use PHPDaemon\Request\RequestHeadersAlreadySent;
9
use PHPDaemon\Utils\MIME;
10
use PHPDaemon\Traits\DeferredEventHandlers;
11
use PHPDaemon\Traits\EventHandlers;
12
use PHPDaemon\Traits\StaticObjectWatchdog;
13
use PHPDaemon\Traits\ClassWatchdog;
14
15
/**
16
 * HTTP request
17
 * @package PHPDaemon\HTTPRequest
18
 * @author  Vasily Zorin <[email protected]>
19
 */
20
abstract class Generic extends \PHPDaemon\Request\Generic
21
{
22
    use DeferredEventHandlers;
23
    use \PHPDaemon\Traits\Sessions;
24
25
    /**
26
     * @var array Status codes
27
     */
28
    protected static $codes = [
29
        100 => 'Continue',
30
        101 => 'Switching Protocols',
31
        200 => 'OK',
32
        201 => 'Created',
33
        202 => 'Accepted',
34
        203 => 'Non-Authoritative Information',
35
        204 => 'No Content',
36
        205 => 'Reset Content',
37
        206 => 'Partial Content',
38
        300 => 'Multiple Choices',
39
        301 => 'Moved Permanently',
40
        302 => 'Found',
41
        303 => 'See Other',
42
        304 => 'Not Modified',
43
        305 => 'Use Proxy',
44
        306 => '(Unused)',
45
        307 => 'Temporary Redirect',
46
        400 => 'Bad Request',
47
        401 => 'Unauthorized',
48
        402 => 'Payment Required',
49
        403 => 'Forbidden',
50
        404 => 'Not Found',
51
        405 => 'Method Not Allowed',
52
        406 => 'Not Acceptable',
53
        407 => 'Proxy Authentication Required',
54
        408 => 'Request Timeout',
55
        409 => 'Conflict',
56
        410 => 'Gone',
57
        411 => 'Length Required',
58
        412 => 'Precondition Failed',
59
        413 => 'Request Entity Too Large',
60
        414 => 'Request-URI Too Long',
61
        415 => 'Unsupported Media Type',
62
        416 => 'Requested Range Not Satisfiable',
63
        417 => 'Expectation Failed',
64
        422 => 'Unprocessable Entity',
65
        423 => 'Locked',
66
        500 => 'Internal Server Error',
67
        501 => 'Not Implemented',
68
        502 => 'Bad Gateway',
69
        503 => 'Service Unavailable',
70
        504 => 'Gateway Timeout',
71
        505 => 'HTTP Version Not Supported',
72
    ];
73
74
    /**
75
     * @var boolean Keepalive?
76
     */
77
    public $keepalive = false;
78
79
    /**
80
     * @var integer Current response length
81
     */
82
    public $responseLength = 0;
83
84
    /**
85
     * @var integer Content length from header() method
86
     */
87
    protected $contentLength;
88
89
    /**
90
     * @var integer Number of outgoing cookie-headers
91
     */
92
    protected $cookieNum = 0;
93
94
    /**
95
     * @var array Replacement pairs for processing some header values in parse_str()
96
     */
97
    public static $hvaltr = ['; ' => '&', ';' => '&', ' ' => '%20'];
98
99
    /**
100
     * @var array State
101
     */
102
    public static $htr = ['-' => '_'];
103
104
    /**
105
     * @var array Outgoing headers
106
     */
107
    protected $headers = ['STATUS' => '200 OK'];
108
109
    /**
110
     * @var boolean Headers sent?
111
     */
112
    protected $headers_sent = false;
113
114
    /**
115
     * @var boolean File name where output started in the file and line variables
116
     */
117
    protected $headers_sent_file;
118
119
    /**
120
     * @var boolean Line number where output started in the file and line variables
121
     */
122
    protected $headers_sent_line;
123
124
    /**
125
     * @var File File pointer to send output (X-Sendfile)
126
     */
127
    protected $sendfp;
128
129
    /**
130
     * @var boolean Frozen input?
131
     */
132
    protected $frozenInput = false;
133
134
    /**
135
     * @var array Content type parameters
136
     */
137
    protected $contype;
138
139
    /**
140
     * Preparing before init
141
     * @param  object $req Source request
142
     * @return void
143
     */
144
    protected function preinit($req)
145
    {
146
        if ($req === null) {
147
            $req                    = new \stdClass;
148
            $req->attrs             = new \stdClass;
149
            $req->attrs->inputDone  = true;
150
            $req->attrs->paramsDone = true;
151
            $req->attrs->chunked    = false;
152
        }
153
154
        $this->attrs = $req->attrs;
155
156
        if ($this->upstream->pool->config->expose->value) {
157
            $this->header('X-Powered-By: phpDaemon/' . Daemon::$version);
158
        }
159
160
        $this->attrs->input->setRequest($this);
161
162
        $this->parseParams();
163
    }
164
165
    /**
166
     * Called when first deferred event used
167
     * @return void
168
     */
169
    public function firstDeferredEventUsed()
170
    {
171
        $this->bind('finish', [$this, 'cleanupDeferredEventHandlers']);
172
    }
173
174
    /**
175
     * Output whole contents of file
176
     * @param  string   $path Path
177
     * @param  callable $cb   Callback
178
     * @param  integer  $pri  Priority
179
     * @return boolean        Success
180
     */
181
    public function sendfile($path, $cb, $pri = EIO_PRI_DEFAULT)
182
    {
183
        if ($this->state === self::STATE_FINISHED) {
184
            return false;
185
        }
186
        try {
187
            $this->header('Content-Type: ' . MIME::get($path));
188
        } catch (RequestHeadersAlreadySent $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
189
        }
190
        if ($this->upstream->checkSendfileCap()) {
191
            FileSystem::sendfile($this->upstream, $path, $cb, function ($file, $length, $handler) {
192
                try {
193
                    $this->header('Content-Length: ' . $length);
194
                } catch (RequestHeadersAlreadySent $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
195
                }
196
                $this->ensureSentHeaders();
197
                $this->upstream->onWriteOnce(function ($conn) use ($handler, $file) {
0 ignored issues
show
Unused Code introduced by
The parameter $conn is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
198
                    $handler($file);
199
                });
200
                return true;
201
            }, 0, null, $pri);
202
            return true;
203
        }
204
        $first = true;
205
        FileSystem::readfileChunked($path, $cb, function ($file, $chunk) use (&$first) { // readed chunk
206
            if ($this->upstream->isFreed()) {
207
                return false;
208
            }
209
            if ($first) {
210
                try {
211
                    $this->header('Content-Length: ' . $file->stat['size']);
212
                } catch (RequestHeadersAlreadySent $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
213
                }
214
                $first = false;
215
            }
216
            $this->out($chunk);
217
            return true;
218
        });
219
        return true;
220
    }
221
222
    /**
223
     * Get cookie by name
224
     * @param  string $name Name of cookie
225
     * @return string       Contents
226
     */
227
    protected function getCookieStr($name)
228
    {
229
        return static::getString($this->attrs->cookie[$name]);
230
    }
231
232
    /**
233
     * Called to check if Request is ready
234
     * @return boolean Ready?
235
     */
236
    public function checkIfReady()
237
    {
238
        if (!$this->attrs->paramsDone || !$this->attrs->inputDone) {
239
            return false;
240
        }
241
        if (isset($this->appInstance->passphrase)) {
242
            if (
243
                    !isset($this->attrs->server['PASSPHRASE'])
244
                    || ($this->appInstance->passphrase !== $this->attrs->server['PASSPHRASE'])
245
            ) {
246
                $this->finish();
247
            }
248
            return false;
249
        }
250
        if ($this->attrs->input->isFrozen()) {
251
            return false;
252
        }
253
        if ($this->sleepTime === 0) {
254
            $this->wakeup();
255
        }
256
        return true;
257
    }
258
259
    /**
260
     * Upload maximum file size
261
     * @return integer
262
     */
263
    public function getUploadMaxSize()
264
    {
265
        return $this->upstream->pool->config->uploadmaxsize->value;
266
    }
267
268
    /**
269
     * Parses GET-query string and other request's headers
270
     * @return void
271
     */
272
    protected function parseParams()
273
    {
274
        if (!isset($this->attrs->server['HTTP_CONTENT_LENGTH'])) {
275
            $this->attrs->contentLength = 0;
276
        } else {
277
            $this->attrs->contentLength = (int)$this->attrs->server['HTTP_CONTENT_LENGTH'];
278
        }
279
        if (
280
                isset($this->attrs->server['CONTENT_TYPE'])
281
                && !isset($this->attrs->server['HTTP_CONTENT_TYPE'])
282
        ) {
283
            $this->attrs->server['HTTP_CONTENT_TYPE'] = $this->attrs->server['CONTENT_TYPE'];
284
        }
285
286 View Code Duplication
        if (isset($this->attrs->server['QUERY_STRING'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
287
            self::parse_str($this->attrs->server['QUERY_STRING'], $this->attrs->get);
288
        }
289
        if (
290
                isset($this->attrs->server['REQUEST_METHOD'])
291
                && ($this->attrs->server['REQUEST_METHOD'] === 'POST' || $this->attrs->server['REQUEST_METHOD'] === 'PUT')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
292
                && isset($this->attrs->server['HTTP_CONTENT_TYPE'])
293
        ) {
294
            $this->attrs->server['REQUEST_METHOD_POST'] = true;
295
            self::parse_str($this->attrs->server['HTTP_CONTENT_TYPE'], $this->contype, true);
296
            $found = false;
297
            foreach ($this->contype as $k => $v) {
298
                if (mb_orig_strpos($k, '/') === false) {
299
                    continue;
300
                }
301
                if (!$found) {
302
                    $found = true;
303
                } else {
304
                    unset($this->contype[$k]);
305
                }
306
            }
307
308
            if (isset($this->contype['multipart/form-data'])
309
                    && (isset($this->contype['boundary']))
310
            ) {
311
                $this->attrs->input->setBoundary($this->contype['boundary']);
312
            }
313
        } else {
314
            $this->attrs->server['REQUEST_METHOD_POST'] = false;
315
        }
316
317 View Code Duplication
        if (isset($this->attrs->server['HTTP_COOKIE'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
            self::parse_str($this->attrs->server['HTTP_COOKIE'], $this->attrs->cookie, true);
0 ignored issues
show
Documentation introduced by
$this->attrs->server['HTTP_COOKIE'] is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
319
        }
320
321
        if (isset($this->attrs->server['HTTP_AUTHORIZATION'])) {
322
            $e = explode(' ', $this->attrs->server['HTTP_AUTHORIZATION'], 2);
323
324
            if (
325
                    ($e[0] === 'Basic')
326
                    && isset($e[1])
327
            ) {
328
                $e[1] = base64_decode($e[1]);
329
                $e    = explode(':', $e[1], 2);
330
331
                if (isset($e[1])) {
332
                    list($this->attrs->server['PHP_AUTH_USER'], $this->attrs->server['PHP_AUTH_PW']) = $e;
333
                }
334
            }
335
        }
336
337
        $this->onParsedParams();
338
    }
339
340
    /**
341
     * Prepares the request body
342
     * @return void
343
     */
344
    public function postPrepare()
345
    {
346
        if (!$this->attrs->server['REQUEST_METHOD_POST']) {
347
            return;
348
        }
349
        if (isset($this->attrs->server['REQUEST_PREPARED_UPLOADS']) && $this->attrs->server['REQUEST_PREPARED_UPLOADS'] === 'nginx') {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
350
            if (isset($this->attrs->server['REQUEST_PREPARED_UPLOADS_URL_PREFIX'])) {
351
                $URLprefix = $this->attrs->server['REQUEST_PREPARED_UPLOADS_URL_PREFIX'];
352
                $l         = mb_orig_strlen($URLprefix);
353
                foreach (['PHP_SELF', 'REQUEST_URI', 'SCRIPT_NAME', 'DOCUMENT_URI'] as $k) {
354
                    if (!isset($this->attrs->server[$k])) {
355
                        continue;
356
                    }
357
                    if (strncmp($this->attrs->server[$k], $URLprefix, $l) === 0) {
358
                        $this->attrs->server[$k] = substr($this->attrs->server[$k], $l - 1);
359
                    }
360
                }
361
            }
362
            $prefix    = 'file.';
363
            $prefixlen = mb_orig_strlen($prefix);
364
            foreach ($this->attrs->post as $k => $v) {
365
                if (strncmp($k, $prefix, $prefixlen) === 0) {
366
                    $e = explode('.', substr($k, $prefixlen));
367
                    if (!isset($e[1])) {
368
                        $e = ['file', $e[0]];
369
                    }
370
                    if (!isset($this->attrs->files[$e[0]])) {
371
                        $this->attrs->files[$e[0]] = ['error' => UPLOAD_ERR_OK];
372
                    }
373
                    $this->attrs->files[$e[0]][$e[1]] = $v;
374
                    unset($this->attrs->post[$k]);
375
                }
376
            }
377
            $uploadTmp = $this->getUploadTempDir();
378
            foreach ($this->attrs->files as $k => &$file) {
379
                if (!isset($file['tmp_name'])
380
                        || !isset($file['name'])
381
                        || !ctype_digit(basename($file['tmp_name']))
382
                        || (mb_orig_strpos(pathinfo($file['tmp_name'], PATHINFO_DIRNAME), $uploadTmp) !== 0)
383
                ) {
384
                    unset($this->attrs->files[$k]);
385
                    continue;
386
                }
387
                FileSystem::open($file['tmp_name'], 'c+!', function ($fp) use (&$file) {
388
                    if (!$fp) {
389
                        return;
390
                    }
391
                    $file['fp'] = $fp;
392
                });
393
            }
394
            unset($file);
395
        }
396
        if (isset($this->attrs->server['REQUEST_BODY_FILE'])
397
                && $this->upstream->pool->config->autoreadbodyfile->value
398
        ) {
399
            $this->readBodyFile();
400
        }
401
    }
402
403
    /**
404
     * Ensure that headers are sent
405
     * @return boolean Were already sent?
406
     */
407
    public function ensureSentHeaders()
408
    {
409
        if ($this->headers_sent) {
410
            return true;
411
        }
412
        if (isset($this->headers['STATUS'])) {
413
            $h = (isset($this->attrs->noHttpVer) && ($this->attrs->noHttpVer) ? 'Status: ' : $this->attrs->server['SERVER_PROTOCOL']) . ' ' . $this->headers['STATUS'] . "\r\n";
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 176 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
414
        } else {
415
            $h = '';
416
        }
417
        $http11 = $this->attrs->server['SERVER_PROTOCOL'] === 'HTTP/1.1';
418
        if ($this->contentLength === null
419
            && $this->upstream->checkChunkedEncCap()
420
            && $http11) {
421
            $this->attrs->chunked = true;
422
        }
423
        if ($this->attrs->chunked) {
424
            $this->header('Transfer-Encoding: chunked');
425
        }
426
427
        if ($http11) {
428
            $connection = isset($this->attrs->server['HTTP_CONNECTION']) ? strtolower($this->attrs->server['HTTP_CONNECTION']) : 'keep-alive';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
429
            if ($connection === 'keep-alive' && $this->upstream->getKeepaliveTimeout() > 0) {
430
                $this->header('Connection: keep-alive');
431
                $this->keepalive = true;
432
            } else {
433
                $this->header('Connection: close');
434
            }
435
        } else {
436
            $this->header('Connection: close');
437
        }
438
439
        foreach ($this->headers as $k => $line) {
440
            if ($k !== 'STATUS') {
441
                $h .= $line . "\r\n";
442
            }
443
        }
444
        $h .= "\r\n";
445
        $this->headers_sent_file = __FILE__;
0 ignored issues
show
Documentation Bug introduced by
The property $headers_sent_file was declared of type boolean, but __FILE__ is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
446
        $this->headers_sent_line = __LINE__;
0 ignored issues
show
Documentation Bug introduced by
The property $headers_sent_line was declared of type boolean, but __LINE__ is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
447
        $this->headers_sent      = true;
448
        $this->upstream->requestOut($this, $h);
449
        return false;
450
    }
451
452
    /**
453
     * Output some data
454
     * @param  string  $s     String to out
455
     * @param  boolean $flush ob_flush?
456
     * @return boolean        Success
457
     */
458
    public function out($s, $flush = true)
459
    {
460
        if ($flush) {
461
            if (!Daemon::$obInStack) { // preventing recursion
462
                ob_flush();
463
            }
464
        }
465
466
        if ($this->aborted) {
467
            return false;
468
        }
469
        if (!isset($this->upstream)) {
470
            return false;
471
        }
472
473
        $l = mb_orig_strlen($s);
474
        $this->responseLength += $l;
475
476
        $this->ensureSentHeaders();
477
478
        if ($this->attrs->chunked) {
479
            for ($o = 0; $o < $l;) {
480
                $c = min($this->upstream->pool->config->chunksize->value, $l - $o);
481
482
                $chunk = dechex($c) . "\r\n"
483
                        . ($c === $l ? $s : mb_orig_substr($s, $o, $c)) // content
484
                        . "\r\n";
485
486
                if ($this->sendfp) {
487
                    $this->sendfp->write($chunk);
488
                } else {
489
                    $this->upstream->requestOut($this, $chunk);
490
                }
491
492
                $o += $c;
493
            }
494
            return true;
495
        } else {
496
            if ($this->sendfp) {
497
                $this->sendfp->write($s);
498
                return true;
499
            }
500
501
            if (Daemon::$compatMode) {
502
                echo $s;
503
                return true;
504
            }
505
506
            return $this->upstream->requestOut($this, $s);
507
        }
508
    }
509
510
    /**
511
     * Called when request's headers parsed
512
     * @return void
513
     */
514
    public function onParsedParams()
515
    {
516
    }
517
518
    /**
519
     * Outputs data with headers (split by \r\n\r\n)
520
     * @param  string  $s Data
521
     * @return boolean    Success
522
     */
523
    public function combinedOut($s)
524
    {
525
        if (!$this->headers_sent) {
526
            $e = explode("\r\n\r\n", $s, 2);
527
            $h = explode("\r\n", $e[0]);
528
529
            foreach ($h as $l) {
530
                $this->header($l);
531
            }
532
533
            if (isset($e[1])) {
534
                return $this->out($e[1]);
535
            }
536
537
            return true;
538
        } else {
539
            return $this->out($s);
540
        }
541
    }
542
543
    /**
544
     * Use chunked encoding
545
     * @return void
546
     */
547
    public function chunked()
548
    {
549
        $this->header('Transfer-Encoding: chunked');
550
        $this->attrs->chunked = true;
551
    }
552
553
    /**
554
     * Called when the request wakes up
555
     * @return void
556
     */
557
    public function onWakeup()
0 ignored issues
show
Coding Style introduced by
onWakeup uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
558
    {
559
        parent::onWakeup();
560
        if (!Daemon::$obInStack) { // preventing recursion
561
            @ob_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
562
        }
563
        $_GET     = & $this->attrs->get;
564
        $_POST    = & $this->attrs->post;
565
        $_COOKIE  = & $this->attrs->cookie;
566
        $_REQUEST = & $this->attrs->request;
567
        $_SESSION = & $this->attrs->session;
568
        $_FILES   = & $this->attrs->files;
569
        $_SERVER  = & $this->attrs->server;
570
    }
571
572
    /**
573
     * Called when the request starts sleep
574
     * @return void
575
     */
576
    public function onSleep()
0 ignored issues
show
Coding Style introduced by
onSleep uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
577
    {
578
        if (!Daemon::$obInStack) { // preventing recursion
579
            @ob_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
580
        }
581
        unset($_GET);
582
        unset($_POST);
583
        unset($_COOKIE);
584
        unset($_REQUEST);
585
        unset($_SESSION);
586
        unset($_FILES);
587
        unset($_SERVER);
588
        parent::onSleep();
589
    }
590
591
    /**
592
     * Send HTTP-status
593
     * @param  integer $code Code
594
     * @throws RequestHeadersAlreadySent
595
     * @return boolean Success
596
     */
597
    public function status($code = 200)
598
    {
599
        if (!isset(self::$codes[$code])) {
600
            return false;
601
        }
602
        $this->header($code . ' ' . self::$codes[$code]);
603
        return true;
604
    }
605
606
    /**
607
     * Checks if headers have been sent
608
     * @param  string  &$file File name
609
     * @param  integer &$line Line in file
610
     * @return boolean        Success
611
     */
612
    public function headers_sent(&$file, &$line)
613
    {
614
        $file = $this->headers_sent_file;
615
        $line = $this->headers_sent_line;
616
        return $this->headers_sent;
617
    }
618
619
    /**
620
     * Return current list of headers
621
     * @return array Headers
622
     */
623
    public function headers_list()
624
    {
625
        return array_values($this->headers);
626
    }
627
628
    /**
629
     * Set the cookie
630
     * @param string  $name     Name of cookie
631
     * @param string  $value    Value
632
     * @param integer $maxage   Optional. Max-Age. Default is 0
633
     * @param string  $path     Optional. Path. Default is empty string
634
     * @param string  $domain   Optional. Domain. Default is empty string
635
     * @param boolean $secure   Optional. Secure. Default is false
636
     * @param boolean $HTTPOnly Optional. HTTPOnly. Default is false
637
     * @return void
638
     */
639 View Code Duplication
    public function setcookie($name, $value = '', $maxage = 0, $path = '', $domain = '', $secure = false, $HTTPOnly = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
640
    {
641
        $this->header(
642
            'Set-Cookie: ' . $name . '=' . rawurlencode($value)
643
            . (empty($domain) ? '' : '; Domain=' . $domain)
644
            . (empty($maxage) ? '' : '; Max-Age=' . $maxage)
645
            . (empty($path) ? '' : '; Path=' . $path)
646
            . (!$secure ? '' : '; Secure')
647
            . (!$HTTPOnly ? '' : '; HttpOnly'), false);
648
    }
649
650
    /**
651
     * Send the header
652
     * @param  string  $s       Header. Example: 'Location: http://php.net/'
653
     * @param  boolean $replace Optional. Replace?
654
     * @param  integer $code    Optional. HTTP response code
0 ignored issues
show
Documentation introduced by
Should the type for parameter $code not be false|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
655
     * @throws \PHPDaemon\Request\RequestHeadersAlreadySent
656
     * @return boolean Success
657
     */
658 View Code Duplication
    public function header($s, $replace = true, $code = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
659
    {
660
        if (!$code) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $code of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
661
            $this->status($code);
0 ignored issues
show
Bug introduced by
It seems like $code defined by parameter $code on line 658 can also be of type false; however, PHPDaemon\HTTPRequest\Generic::status() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
662
        }
663
664
        if ($this->headers_sent) {
665
            throw new RequestHeadersAlreadySent;
666
        }
667
        $s = strtr($s, "\r\n", '  ');
668
669
        $e = explode(':', $s, 2);
670
671
        if (!isset($e[1])) {
672
            $e[0] = 'STATUS';
673
674
            if (strncmp($s, 'HTTP/', 5) === 0) {
675
                $s = substr($s, 9);
676
            }
677
        }
678
679
        $k = strtr(strtoupper($e[0]), Generic::$htr);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
680
681
        if ($k === 'CONTENT_TYPE') {
682
            self::parse_str(strtolower($e[1]), $ctype, true);
683
            if (!isset($ctype['charset'])) {
684
                $ctype['charset'] = $this->upstream->pool->config->defaultcharset->value;
685
686
                $s = $e[0] . ': ';
687
                $i = 0;
688
                foreach ($ctype as $k => $v) {
689
                    $s .= ($i > 0 ? '; ' : '') . $k . ($v !== '' ? '=' . $v : '');
690
                    ++$i;
691
                }
692
            }
693
        }
694
695
        if ($k === 'SET_COOKIE') {
696
            $k .= '_' . ++$this->cookieNum;
697
        } elseif (!$replace && isset($this->headers[$k])) {
698
            return false;
699
        }
700
701
        $this->headers[$k] = $s;
702
703
        if ($k === 'CONTENT_LENGTH') {
704
            $this->contentLength = (int)$e[1];
705
        } elseif ($k === 'LOCATION') {
706
            $this->status(301);
707
        }
708
709
        if (Daemon::$compatMode) {
710
            is_callable('header_native') ? header_native($s) : header($s);
711
        }
712
713
        return true;
714
    }
715
716
    /**
717
     * Removes a header
718
     * @param  string $s Header name. Example: 'Location'
719
     * @return void
720
     */
721
    public function removeHeader($s)
722
    {
723
        unset($this->headers[strtr(strtoupper($s), Generic::$htr)]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
724
    }
725
726
    /**
727
     * Converts human-readable representation of size to number of bytes
728
     * @param  string $value String of size
729
     * @return integer
730
     */
731 View Code Duplication
    public static function parseSize($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
732
    {
733
        $l = substr($value, -1);
734
735
        if ($l === 'b' || $l === 'B') {
736
            return ((int)substr($value, 0, -1));
737
        }
738
739
        if ($l === 'k') {
740
            return ((int)substr($value, 0, -1) * 1000);
741
        }
742
743
        if ($l === 'K') {
744
            return ((int)substr($value, 0, -1) * 1024);
745
        }
746
747
        if ($l === 'm') {
748
            return ((int)substr($value, 0, -1) * 1000 * 1000);
749
        }
750
751
        if ($l === 'M') {
752
            return ((int)substr($value, 0, -1) * 1024 * 1024);
753
        }
754
755
        if ($l === 'g') {
756
            return ((int)substr($value, 0, -1) * 1000 * 1000 * 1000);
757
        }
758
759
        if ($l === 'G') {
760
            return ((int)substr($value, 0, -1) * 1024 * 1024 * 1024);
761
        }
762
763
        return (int)$value;
764
    }
765
766
    /**
767
     * Called when file upload started
768
     * @param  Input $in Input buffer
769
     * @return void
770
     */
771
    public function onUploadFileStart($in)
772
    {
773
        $this->freezeInput();
774
        FileSystem::tempnam(ini_get('upload_tmp_dir'), 'php', function ($fp) use ($in) {
775
            if (!$fp) {
776
                $in->curPart['fp']    = false;
777
                $in->curPart['error'] = UPLOAD_ERR_NO_TMP_DIR;
778
            } else {
779
                $in->curPart['fp']       = $fp;
780
                $in->curPart['tmp_name'] = $fp->path;
781
            }
782
            $this->unfreezeInput();
783
        });
784
    }
785
786
    /**
787
     * Called when chunk of incoming file has arrived
788
     * @param  Input   $in   Input buffer
789
     * @param  boolean $last Last?
790
     * @return void
791
     */
792
    public function onUploadFileChunk($in, $last = false)
793
    {
794
        if ($in->curPart['error'] !== UPLOAD_ERR_OK) {
795
            // just drop the chunk
796
            return;
797
        }
798
        $cb = function ($fp, $result) use ($last, $in) {
0 ignored issues
show
Unused Code introduced by
The parameter $fp is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $result is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
799
            if ($last) {
800
                unset($in->curPart['fp']);
801
            }
802
            $this->unfreezeInput();
803
        };
804
        if ($in->writeChunkToFd($in->curPart['fp']->getFd())) {
805
            // We had written via internal method
806
            return;
807
        }
808
        // Internal method is not available, let's get chunk data into $chunk and then use File->write()
809
        $chunk = $in->getChunkString();
810
        if ($chunk === false) {
811
            return;
812
        }
813
        $this->freezeInput();
814
        $in->curPart['fp']->write($chunk, $cb);
815
    }
816
817
    /**
818
     * Freeze input
819
     * @return void
820
     */
821
    protected function freezeInput()
822
    {
823
        $this->upstream->freezeInput();
824
        $this->attrs->input->freeze();
825
    }
826
827
    /**
828
     * Unfreeze input
829
     * @return void
830
     */
831
    protected function unfreezeInput()
832
    {
833
        $this->upstream->unfreezeInput();
834
        if (isset($this->attrs->input)) {
835
            $this->attrs->input->unfreeze();
836
        }
837
    }
838
839
    /**
840
     * Returns path to directory of temporary upload files
841
     * @return string
842
     */
843
    public function getUploadTempDir()
844
    {
845
        if ($r = ini_get('upload_tmp_dir')) {
846
            return $r;
847
        }
848
        return sys_get_temp_dir();
849
    }
850
851
    /**
852
     * Tells whether the file was uploaded via HTTP POST
853
     * @param  string  $path The filename being checked
854
     * @return boolean       Whether if this is uploaded file
855
     */
856
    public function isUploadedFile($path)
857
    {
858
        if (!$path) {
859
            return false;
860
        }
861
        if (mb_orig_strpos($path, $this->getUploadTempDir() . '/') !== 0) {
862
            return false;
863
        }
864
        foreach ($this->attrs->files as $file) {
865
            if ($file['tmp_name'] === $path) {
866
                return true;
867
            }
868
        }
869
        return false;
870
    }
871
872
    /**
873
     * Moves an uploaded file to a new location
874
     * @param  string  $filename The filename of the uploaded file
875
     * @param  string  $dest     The destination of the moved file
876
     * @return boolean           Success
0 ignored issues
show
Documentation introduced by
Should the return type not be resource|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
877
     */
878
    public function moveUploadedFile($filename, $dest)
879
    {
880
        if (!$this->isUploadedFile($filename)) {
881
            return false;
882
        }
883
        return FileSystem::rename($filename, $dest);
884
    }
885
886
    /**
887
     * Read request body from the file given in REQUEST_BODY_FILE parameter
888
     * @return boolean Success
889
     */
890
    public function readBodyFile()
891
    {
892
        if (!isset($this->attrs->server['REQUEST_BODY_FILE'])) {
893
            return false;
894
        }
895
        FileSystem::readfileChunked($this->attrs->server['REQUEST_BODY_FILE'],
896
            function ($file, $success) {
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $success is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
897
                $this->attrs->inputDone = true;
898
                if ($this->sleepTime === 0) {
899
                    $this->wakeup();
900
                }
901
            },
902
            function ($file, $chunk) { // readed chunk
903
                $this->stdin($chunk);
0 ignored issues
show
Documentation Bug introduced by
The method stdin does not exist on object<PHPDaemon\HTTPRequest\Generic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
904
            }
905
        );
906
        return true;
907
    }
908
909
    /**
910
     * Replacement for default parse_str(), it supoorts UCS-2 like this: %uXXXX
911
     * @param  string  $s      String to parse
912
     * @param  array   &$var   Reference to the resulting array
913
     * @param  boolean $header Header-style string
914
     * @return void
915
     */
916
    public static function parse_str($s, &$var, $header = false)
917
    {
918
        static $cb;
919
        if ($cb === null) {
920
            $cb = function ($m) {
921
                return urlencode(html_entity_decode('&#' . hexdec($m[1]) . ';', ENT_NOQUOTES, 'utf-8'));
922
            };
923
        }
924
        if ($header) {
925
            $s = strtr($s, Generic::$hvaltr);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
926
        }
927
        if (
928
                (stripos($s, '%u') !== false)
929
                && preg_match('~(%u[a-f\d]{4}|%[c-f][a-f\d](?!%[89a-f][a-f\d]))~is', $s, $m)
930
        ) {
931
            $s = preg_replace_callback('~%(u[a-f\d]{4}|[a-f\d]{2})~i', $cb, $s);
932
        }
933
        parse_str($s, $var);
934
    }
935
936
    /**
937
     * Called after request finish
938
     * @param  callable $cb Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
939
     * @return void
940
     */
941
    protected function postFinishHandler($cb = null)
942
    {
943
        if (!$this->headers_sent) {
944
            $this->out('');
945
        }
946
        $this->sendfp       = null;
947
        if (isset($this->attrs->files)) {
948
            foreach ($this->attrs->files as $f) {
949
                if (isset($f['tmp_name'])) {
950
                    FileSystem::unlink($f['tmp_name']);
951
                }
952
            }
953
        }
954
        if (isset($this->attrs->session)) {
955
            $this->sessionCommit($cb);
956
        } else {
957
            $cb === null || $cb();
958
        }
959
    }
960
}
961