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

Connection::httpReadHeaders()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 21
Code Lines 14

Duplication

Lines 4
Ratio 19.05 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 7
dl 4
loc 21
rs 7.551
eloc 14
c 2
b 1
f 0
nc 5
nop 0
1
<?php
2
namespace PHPDaemon\Servers\WebSocket;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\HTTPRequest\Generic;
6
use PHPDaemon\WebSocket\Route;
7
use PHPDaemon\Request\RequestHeadersAlreadySent;
8
9
class Connection extends \PHPDaemon\Network\Connection
10
{
11
    use \PHPDaemon\Traits\DeferredEventHandlers;
12
    use \PHPDaemon\Traits\Sessions;
13
14
    /**
15
     * @var integer Timeout
16
     */
17
    protected $timeout = 120;
18
19
    protected $handshaked = false;
20
    protected $route;
21
    protected $writeReady = true;
22
    protected $extensions = [];
23
    protected $extensionsCleanRegex = '/(?:^|\W)x-webkit-/iS';
24
    
25
    protected $headers = [];
26
    protected $headers_sent = false;
27
    
28
    /**
29
     * @var array _SERVER
30
     */
31
    public $server = [];
32
33
    /**
34
     * @var array _COOKIE
35
     */
36
    public $cookie = [];
37
38
    /**
39
     * @var array _GET
40
     */
41
    public $get = [];
42
    
43
44
    protected $policyReqNotFound = false;
45
    protected $currentHeader;
46
    protected $EOL = "\r\n";
47
48
    /**
49
     * @var boolean Is this connection running right now?
50
     */
51
    protected $running = false;
52
53
    /**
54
     * State: first line
55
     */
56
    const STATE_FIRSTLINE  = 1;
57
58
    /**
59
     * State: headers
60
     */
61
    const STATE_HEADERS    = 2;
62
63
    /**
64
     * State: content
65
     */
66
    const STATE_CONTENT    = 3;
67
68
    /**
69
     * State: prehandshake
70
     */
71
    const STATE_PREHANDSHAKE = 5;
72
73
    /**
74
     * State: handshaked
75
     */
76
    const STATE_HANDSHAKED = 6;
77
78
    const STRING = null;
79
80
    const BINARY = null;
81
82
    /**
83
     * @var integer Content length from header() method
84
     */
85
    protected $contentLength;
86
87
    /**
88
     * @var integer Number of outgoing cookie-headers
89
     */
90
    protected $cookieNum = 0;
91
92
    /**
93
     * @var array Replacement pairs for processing some header values in parse_str()
94
     */
95
    public static $hvaltr = ['; ' => '&', ';' => '&', ' ' => '%20'];
96
97
    /**
98
     * Called when the stream is handshaked (at low-level), and peer is ready to recv. data
99
     * @return void
100
     */
101
    public function onReady()
102
    {
103
        $this->setWatermark(null, $this->pool->maxAllowedPacket + 100);
104
    }
105
106
    /**
107
     * Get real frame type identificator
108
     * @param $type
109
     * @return integer
110
     */
111
    public function getFrameType($type)
112
    {
113
        if (is_int($type)) {
114
            return $type;
115
        }
116
        if ($type === null) {
117
            $type = 'STRING';
118
        }
119
        $frametype = @constant(get_class($this) . '::' . $type);
120
        if ($frametype === null) {
121
            Daemon::log(__METHOD__ . ' : Undefined frametype "' . $type . '"');
122
        }
123
        return $frametype;
124
    }
125
126
127
    /**
128
     * Called when connection is inherited from HTTP request
129
     * @param  object $req
130
     * @return void
131
     */
132 View Code Duplication
    public function onInheritanceFromRequest($req)
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...
133
    {
134
        $this->state  = self::STATE_HEADERS;
135
        $this->addr   = $req->attrs->server['REMOTE_ADDR'];
136
        $this->server = $req->attrs->server;
137
        $this->get = $req->attrs->get;
138
        $this->prependInput("\r\n");
139
        $this->onRead();
140
    }
141
142
    /**
143
     * Sends a frame.
144
     * @param  string   $data  Frame's data.
145
     * @param  string   $type  Frame's type. ("STRING" OR "BINARY")
0 ignored issues
show
Documentation introduced by
Should the type for parameter $type not be string|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...
146
     * @param  callable $cb    Optional. Callback called when the frame is received by client.
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...
147
     * @callback $cb ( )
148
     * @return boolean         Success.
149
     */
150
    public function sendFrame($data, $type = null, $cb = null)
151
    {
152
        return false;
153
    }
154
155
    /**
156
     * Event of Connection.
157
     * @return void
158
     */
159
    public function onFinish()
160
    {
161
        $this->sendFrame('', 'CONNCLOSE');
162
        
163
        if ($this->route) {
164
            $this->route->onFinish();
165
        }
166
        $this->route = null;
167
    }
168
169
    /**
170
     * Uncaught exception handler
171
     * @param  Exception $e
172
     * @return boolean      Handled?
173
     */
174
    public function handleException($e)
175
    {
176
        if (!isset($this->route)) {
177
            return false;
178
        }
179
        return $this->route->handleException($e);
180
    }
181
182
    /**
183
     * Called when new frame received.
184
     * @param  string $data Frame's data.
185
     * @param  string $type Frame's type ("STRING" OR "BINARY").
186
     * @return boolean      Success.
187
     */
188
    public function onFrame($data, $type)
189
    {
190
        if (!isset($this->route)) {
191
            return false;
192
        }
193
        try {
194
            $this->route->onWakeup();
195
            $this->route->onFrame($data, $type);
196
        } catch (\Exception $e) {
197
            Daemon::uncaughtExceptionHandler($e);
198
        }
199
        if ($this->route) {
200
            $this->route->onSleep();
201
        }
202
        return true;
203
    }
204
205
    /**
206
     * Called when the worker is going to shutdown.
207
     * @return boolean Ready to shutdown ?
208
     */
209
    public function gracefulShutdown()
210
    {
211
        if ((!$this->route) || $this->route->gracefulShutdown()) {
212
            $this->finish();
213
            return true;
214
        }
215
        return false;
216
    }
217
218
219
    /**
220
     * Called when we're going to handshake.
221
     * @return boolean               Handshake status
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

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...
222
     */
223
    public function handshake()
224
    {
225
        $this->route = $this->pool->getRoute($this->server['DOCUMENT_URI'], $this);
226 View Code Duplication
        if (!$this->route) {
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...
227
            Daemon::$process->log(get_class($this) . '::' . __METHOD__ . ' : Cannot handshake session for client "' . $this->addr . '"');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 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...
228
            $this->finish();
229
            return false;
230
        }
231
232
        if (method_exists($this->route, 'onBeforeHandshake')) {
233
            $this->route->onWakeup();
234
            $ret = $this->route->onBeforeHandshake(function () {
235
                $this->handshakeAfter();
236
            });
237
            if ($this->route) {
238
                $this->route->onSleep();
239
            }
240
            if ($ret !== false) {
241
                return;
242
            }
243
        }
244
245
        $this->handshakeAfter();
246
    }
247
248
    protected function handshakeAfter()
249
    {
250
        $extraHeaders = '';
251
        foreach ($this->headers as $k => $line) {
252
            if ($k !== 'STATUS') {
253
                $extraHeaders .= $line . "\r\n";
254
            }
255
        }
256
257 View Code Duplication
        if (!$this->sendHandshakeReply($extraHeaders)) {
0 ignored issues
show
Bug introduced by
The method sendHandshakeReply() does not exist on PHPDaemon\Servers\WebSocket\Connection. Did you maybe mean handshake()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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...
258
            Daemon::$process->log(get_class($this) . '::' . __METHOD__ . ' : Handshake protocol failure for client "' . $this->addr . '"');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 139 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...
259
            $this->finish();
260
            return false;
261
        }
262
263
        $this->handshaked = true;
264
        $this->headers_sent = true;
265
        $this->state = static::STATE_HANDSHAKED;
266
        if (is_callable([$this->route, 'onHandshake'])) {
267
            $this->route->onWakeup();
0 ignored issues
show
Bug introduced by
The method onWakeup cannot be called on $this->route (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
268
            $this->route->onHandshake();
0 ignored issues
show
Bug introduced by
The method onHandshake cannot be called on $this->route (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
269
            if ($this->route) {
270
                $this->route->onSleep();
271
            }
272
        }
273
        return true;
274
    }
275
    
276
    /**
277
     * Send Bad request
278
     * @return void
279
     */
280
    public function badRequest()
281
    {
282
        $this->state = self::STATE_STANDBY;
283
        $this->write("400 Bad Request\r\n\r\n<html><head><title>400 Bad Request</title></head><body bgcolor=\"white\"><center><h1>400 Bad Request</h1></center></body></html>");
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...
284
        $this->finish();
285
    }
286
287
    /**
288
     * Read first line of HTTP request
289
     * @return boolean|null Success
290
     */
291
    protected function httpReadFirstline()
292
    {
293
        if (($l = $this->readline()) === null) {
294
            return null;
295
        }
296
        $e = explode(' ', $l);
297
        $u = isset($e[1]) ? parse_url($e[1]) : false;
298
        if ($u === false) {
299
            $this->badRequest();
300
            return false;
301
        }
302
        if (!isset($u['path'])) {
303
            $u['path'] = null;
304
        }
305
        if (isset($u['host'])) {
306
            $this->server['HTTP_HOST'] = $u['host'];
307
        }
308
        $srv                       = & $this->server;
309
        $srv['REQUEST_METHOD']     = $e[0];
310
        $srv['REQUEST_TIME']       = time();
311
        $srv['REQUEST_TIME_FLOAT'] = microtime(true);
312
        $srv['REQUEST_URI']        = $u['path'] . (isset($u['query']) ? '?' . $u['query'] : '');
313
        $srv['DOCUMENT_URI']       = $u['path'];
314
        $srv['PHP_SELF']           = $u['path'];
315
        $srv['QUERY_STRING']       = isset($u['query']) ? $u['query'] : null;
316
        $srv['SCRIPT_NAME']        = $srv['DOCUMENT_URI'] = isset($u['path']) ? $u['path'] : '/';
317
        $srv['SERVER_PROTOCOL']    = isset($e[2]) ? $e[2] : 'HTTP/1.1';
318
        $srv['REMOTE_ADDR']        = $this->addr;
319
        $srv['REMOTE_PORT']        = $this->port;
320
        return true;
321
    }
322
323
    /**
324
     * Read headers line-by-line
325
     * @return boolean|null Success
326
     */
327
    protected function httpReadHeaders()
328
    {
329
        while (($l = $this->readLine()) !== null) {
330
            if ($l === '') {
331
                return true;
332
            }
333
            $e = explode(': ', $l);
334
            if (isset($e[1])) {
335
                $this->currentHeader                = 'HTTP_' . strtoupper(strtr($e[0], Generic::$htr));
336
                $this->server[$this->currentHeader] = $e[1];
337 View Code Duplication
            } elseif (($e[0][0] === "\t" || $e[0][0] === "\x20") && $this->currentHeader) {
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...
338
                // multiline header continued
339
                $this->server[$this->currentHeader] .= $e[0];
340
            } else {
341
                // whatever client speaks is not HTTP anymore
342
                $this->badRequest();
343
                return false;
344
            }
345
        }
346
        return null;
347
    }
348
349
    /**
350
     * Called when new data received.
351
     * @return void
352
     */
353
    protected function onRead()
354
    {
355 View Code Duplication
        if (!$this->policyReqNotFound) {
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...
356
            $d = $this->drainIfMatch("<policy-file-request/>\x00");
357
            if ($d === null) { // partially match
358
                return;
359
            }
360
            if ($d) {
361
                if (($FP = \PHPDaemon\Servers\FlashPolicy\Pool::getInstance($this->pool->config->fpsname->value, false)) && $FP->policyData) {
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...
362
                    $this->write($FP->policyData . "\x00");
363
                }
364
                $this->finish();
365
                return;
366
            } else {
367
                $this->policyReqNotFound = true;
368
            }
369
        }
370
        start:
371
        if ($this->finished) {
372
            return;
373
        }
374
        if ($this->state === self::STATE_STANDBY) {
375
            $this->state = self::STATE_FIRSTLINE;
376
        }
377
        if ($this->state === self::STATE_FIRSTLINE) {
378
            if (!$this->httpReadFirstline()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->httpReadFirstline() of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
379
                return;
380
            }
381
            $this->state = self::STATE_HEADERS;
382
        }
383
384 View Code Duplication
        if ($this->state === self::STATE_HEADERS) {
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...
385
            if (!$this->httpReadHeaders()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->httpReadHeaders() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
386
                return;
387
            }
388
            if (!$this->httpProcessHeaders()) {
389
                $this->finish();
390
                return;
391
            }
392
            $this->state = self::STATE_CONTENT;
393
        }
394
        if ($this->state === self::STATE_CONTENT) {
395
            $this->state = self::STATE_PREHANDSHAKE;
396
        }
397
    }
398
399
    /**
400
     * Process headers
401
     * @return bool
402
     */
403
    protected function httpProcessHeaders()
404
    {
405
        $this->state = self::STATE_PREHANDSHAKE;
406
        if (isset($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS'])) {
407
            $str              = strtolower($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS']);
408
            $str              = preg_replace($this->extensionsCleanRegex, '', $str);
409
            $this->extensions = explode(', ', $str);
410
        }
411
        if (!isset($this->server['HTTP_CONNECTION'])
412
                || (!preg_match('~(?:^|\W)Upgrade(?:\W|$)~i', $this->server['HTTP_CONNECTION'])) // "Upgrade" is not always alone (ie. "Connection: Keep-alive, Upgrade")
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 169 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...
413
                || !isset($this->server['HTTP_UPGRADE'])
414
                || (strtolower($this->server['HTTP_UPGRADE']) !== 'websocket') // Lowercase comparison iss important
415
        ) {
416
            $this->finish();
417
            return false;
418
        }
419 View Code Duplication
        if (isset($this->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...
420
            Generic::parse_str(strtr($this->server['HTTP_COOKIE'], Generic::$hvaltr), $this->cookie);
421
        }
422
        if (isset($this->server['QUERY_STRING'])) {
423
            Generic::parse_str($this->server['QUERY_STRING'], $this->get);
424
        }
425
        // ----------------------------------------------------------
426
        // Protocol discovery, based on HTTP headers...
427
        // ----------------------------------------------------------
428
        if (isset($this->server['HTTP_SEC_WEBSOCKET_VERSION'])) { // HYBI
429
            if ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] === '8') { // Version 8 (FF7, Chrome14)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
430
                $this->switchToProtocol('V13');
431
            } elseif ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] === '13') { // newest protocol
432
                $this->switchToProtocol('V13');
433
            } else {
434
                Daemon::$process->log(get_class($this) . '::' . __METHOD__ . " : Websocket protocol version " . $this->server['HTTP_SEC_WEBSOCKET_VERSION'] . ' is not yet supported for client "' . $this->addr . '"');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 216 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...
435
                $this->finish();
436
                return false;
437
            }
438 View Code Duplication
        } elseif (!isset($this->server['HTTP_SEC_WEBSOCKET_KEY1']) || !isset($this->server['HTTP_SEC_WEBSOCKET_KEY2'])) {
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...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 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...
439
            $this->switchToProtocol('VE');
440
        } else { // Defaulting to HIXIE (Safari5 and many non-browser clients...)
441
            $this->switchToProtocol('V0');
442
        }
443
        // ----------------------------------------------------------
444
        // End of protocol discovery
445
        // ----------------------------------------------------------
446
        return true;
447
    }
448
449
    protected function switchToProtocol($proto)
450
    {
451
        $class = '\\PHPDaemon\\Servers\\WebSocket\\Protocols\\' . $proto;
452
        $conn  = new $class(null, $this->pool);
453
        $this->pool->attach($conn);
454
        $conn->setFd($this->getFd(), $this->getBev());
455
        $this->unsetFd();
456
        $this->pool->detach($this);
457
        $conn->onInheritance($this);
458
    }
459
460 View Code Duplication
    public function onInheritance($conn)
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...
461
    {
462
        $this->server = $conn->server;
463
        $this->cookie = $conn->cookie;
464
        $this->get = $conn->get;
465
        $this->state = self::STATE_PREHANDSHAKE;
466
        $this->addr = $conn->addr;
467
        $this->onRead();
468
    }
469
470
471
    /**
472
     * Send HTTP-status
473
     * @throws RequestHeadersAlreadySent
474
     * @param  integer $code Code
475
     * @return boolean       Success
476
     */
477
    public function status($code = 200)
0 ignored issues
show
Unused Code introduced by
The parameter $code 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...
478
    {
479
        return false;
480
    }
481
482
    /**
483
     * Send the header
484
     * @param  string  $s       Header. Example: 'Location: http://php.net/'
485
     * @param  boolean $replace Optional. Replace?
486
     * @param  boolean $code    Optional. HTTP response code
487
     * @throws \PHPDaemon\Request\RequestHeadersAlreadySent
488
     * @return boolean          Success
489
     */
490 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...
491
    {
492
        if ($code) {
493
            $this->status($code);
0 ignored issues
show
Documentation introduced by
$code is of type boolean, but the function expects a integer.

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...
Unused Code introduced by
The call to the method PHPDaemon\Servers\WebSocket\Connection::status() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
494
        }
495
496
        if ($this->headers_sent) {
497
            throw new RequestHeadersAlreadySent;
498
        }
499
        $s = strtr($s, "\r\n", '  ');
500
501
        $e = explode(':', $s, 2);
502
503
        if (!isset($e[1])) {
504
            $e[0] = 'STATUS';
505
506
            if (strncmp($s, 'HTTP/', 5) === 0) {
507
                $s = substr($s, 9);
508
            }
509
        }
510
511
        $k = strtr(strtoupper($e[0]), Generic::$htr);
512
513
        if ($k === 'CONTENT_TYPE') {
514
            self::parse_str(strtolower($e[1]), $ctype, true);
0 ignored issues
show
Bug introduced by
The variable $ctype seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
515
            if (!isset($ctype['charset'])) {
0 ignored issues
show
Bug introduced by
The variable $ctype seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
516
                $ctype['charset'] = $this->upstream->pool->config->defaultcharset->value;
0 ignored issues
show
Documentation introduced by
The property upstream does not exist on object<PHPDaemon\Servers\WebSocket\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Coding Style Comprehensibility introduced by
$ctype was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ctype = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
517
518
                $s = $e[0] . ': ';
519
                $i = 0;
520
                foreach ($ctype as $k => $v) {
521
                    $s .= ($i > 0 ? '; ' : '') . $k . ($v !== '' ? '=' . $v : '');
522
                    ++$i;
523
                }
524
            }
525
        }
526
        if ($k === 'SET_COOKIE') {
527
            $k .= '_' . ++$this->cookieNum;
528
        } elseif (!$replace && isset($this->headers[$k])) {
529
            return false;
530
        }
531
532
        $this->headers[$k] = $s;
533
534
        if ($k === 'CONTENT_LENGTH') {
535
            $this->contentLength = (int)$e[1];
536
        } elseif ($k === 'LOCATION') {
537
            $this->status(301);
0 ignored issues
show
Unused Code introduced by
The call to the method PHPDaemon\Servers\WebSocket\Connection::status() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
538
        }
539
540
        if (Daemon::$compatMode) {
541
            is_callable('header_native') ? header_native($s) : header($s);
542
        }
543
544
        return true;
545
    }
546
}
547