Completed
Push — master ( 28d349...e7528b )
by Vasily
06:31 queued 02:31
created

Connection::handshake()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 23
Code Lines 15

Duplication

Lines 5
Ratio 21.74 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
c 7
b 1
f 0
dl 5
loc 23
rs 8.5906
cc 5
eloc 15
nc 6
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
	use \PHPDaemon\Traits\DeferredEventHandlers;
11
	use \PHPDaemon\Traits\Sessions;
12
13
	/**
14
	 * @var integer Timeout
15
	 */
16
	protected $timeout = 120;
17
18
	protected $handshaked = false;
19
	protected $route;
20
	protected $writeReady = true;
21
	protected $extensions = [];
22
	protected $extensionsCleanRegex = '/(?:^|\W)x-webkit-/iS';
23
	
24
	protected $headers = [];
25
	protected $headers_sent = false;
26
	
27
	/**
28
	 * @var array _SERVER
29
	 */
30
	public $server = [];
31
32
	/**
33
	 * @var array _COOKIE
34
	 */
35
	public $cookie = [];
36
37
	/**
38
	 * @var array _GET
39
	 */
40
	public $get = [];
41
	
42
43
	protected $policyReqNotFound = false;
44
	protected $currentHeader;
45
	protected $EOL = "\r\n";
46
47
	/**
48
	 * @var boolean Is this connection running right now?
49
	 */
50
	protected $running = false;
51
52
	/**
53
	 * State: first line
54
	 */
55
	const STATE_FIRSTLINE  = 1;
56
57
	/**
58
	 * State: headers
59
	 */
60
	const STATE_HEADERS    = 2;
61
62
	/**
63
	 * State: content
64
	 */
65
	const STATE_CONTENT    = 3;
66
67
	/**
68
	 * State: prehandshake
69
	 */
70
	const STATE_PREHANDSHAKE = 5;
71
72
	/**
73
	 * State: handshaked
74
	 */
75
	const STATE_HANDSHAKED = 6;
76
77
	const STRING = NULL;
78
79
	const BINARY = NULL;
80
81
	/**
82
	 * @var integer Content length from header() method
83
	 */
84
	protected $contentLength;
85
86
	/**
87
	 * @var integer Number of outgoing cookie-headers
88
	 */
89
	protected $cookieNum = 0;
90
91
	/**
92
	 * @var array Replacement pairs for processing some header values in parse_str()
93
	 */
94
	public static $hvaltr = ['; ' => '&', ';' => '&', ' ' => '%20'];
95
96
	/**
97
	 * Called when the stream is handshaked (at low-level), and peer is ready to recv. data
98
	 * @return void
99
	 */
100
	public function onReady() {
101
		$this->setWatermark(null, $this->pool->maxAllowedPacket + 100);
102
	}
103
104
	/**
105
	 * Get real frame type identificator
106
	 * @param $type
107
	 * @return integer
108
	 */
109
	public function getFrameType($type) {
110
		if (is_int($type)) {
111
			return $type;
112
		}
113
		if ($type === null) {
114
			$type = 'STRING';
115
		}
116
		$frametype = @constant(get_class($this) . '::' . $type);
117
		if ($frametype === null) {
118
			Daemon::log(__METHOD__ . ' : Undefined frametype "' . $type . '"');
119
		}
120
		return $frametype;
121
	}
122
123
124
	/**
125
	 * Called when connection is inherited from HTTP request
126
	 * @param  object $req
127
	 * @return void
128
	 */
129 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...
130
		$this->state  = self::STATE_HEADERS;
131
		$this->addr   = $req->attrs->server['REMOTE_ADDR'];
132
		$this->server = $req->attrs->server;
133
		$this->get = $req->attrs->get;
134
		$this->prependInput("\r\n");
135
		$this->onRead();
136
	}
137
138
	/**
139
	 * Sends a frame.
140
	 * @param  string   $data  Frame's data.
141
	 * @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...
142
	 * @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...
143
	 * @callback $cb ( )
144
	 * @return boolean         Success.
145
	 */
146
	public function sendFrame($data, $type = null, $cb = null) {
147
		return false;
148
	}
149
150
	/**
151
	 * Event of Connection.
152
	 * @return void
153
	 */
154
	public function onFinish() {
155
156
		$this->sendFrame('', 'CONNCLOSE');
157
		
158
		if ($this->route) {
159
			$this->route->onFinish();
160
		}
161
		$this->route = null;
162
	}
163
164
	/**
165
	 * Uncaught exception handler
166
	 * @param  Exception $e
167
	 * @return boolean      Handled?
168
	 */
169
	public function handleException($e) {
170
		if (!isset($this->route)) {
171
			return false;
172
		}
173
		return $this->route->handleException($e);
174
	}
175
176
	/**
177
	 * Called when new frame received.
178
	 * @param  string $data Frame's data.
179
	 * @param  string $type Frame's type ("STRING" OR "BINARY").
180
	 * @return boolean      Success.
181
	 */
182
	public function onFrame($data, $type) {
183
		if (!isset($this->route)) {
184
			return false;
185
		}
186
		try {
187
			$this->route->onWakeup();
188
			$this->route->onFrame($data, $type);
189
		} catch (\Exception $e) {
190
			Daemon::uncaughtExceptionHandler($e);
191
		}
192
		if ($this->route) {
193
			$this->route->onSleep();
194
		}
195
		return true;
196
	}
197
198
	/**
199
	 * Called when the worker is going to shutdown.
200
	 * @return boolean Ready to shutdown ?
201
	 */
202
	public function gracefulShutdown() {
203
		if ((!$this->route) || $this->route->gracefulShutdown()) {
204
			$this->finish();
205
			return true;
206
		}
207
		return FALSE;
208
	}
209
210
211
	/**
212
	 * Called when we're going to handshake.
213
	 * @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...
214
	 */
215
	public function handshake() {
216
		$this->route = $this->pool->getRoute($this->server['DOCUMENT_URI'], $this);
217 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...
218
			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 128 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...
219
			$this->finish();
220
			return false;
221
		}
222
223
		if (method_exists($this->route, 'onBeforeHandshake')) {
224
			$this->route->onWakeup();
225
			$ret = $this->route->onBeforeHandshake(function() {
226
				$this->handshakeAfter();
227
			});
228
			if ($this->route) {
229
				$this->route->onSleep();
230
			}
231
			if ($ret !== false) {
232
				return;
233
			}
234
		}
235
236
		$this->handshakeAfter();
237
	}
238
239
	protected function handshakeAfter() {
240
		$extraHeaders = '';
241
		foreach ($this->headers as $k => $line) {
242
			if ($k !== 'STATUS') {
243
				$extraHeaders .= $line . "\r\n";
244
			}
245
		}
246
247 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...
248
			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 130 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...
249
			$this->finish();
250
			return false;
251
		}
252
253
		$this->handshaked = true;
254
		$this->headers_sent = true;
255
		$this->state = static::STATE_HANDSHAKED;
256
		if (is_callable([$this->route, 'onHandshake'])) {
257
			$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...
258
			$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...
259
			if ($this->route) {
260
				$this->route->onSleep();
261
			}
262
		}
263
		return true;
264
	}
265
	
266
	/**
267
	 * Send Bad request
268
	 * @return void
269
	 */
270
	public function badRequest() {
271
		$this->state = self::STATE_STANDBY;
272
		$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 170 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...
273
		$this->finish();
274
	}
275
276
	/**
277
	 * Read first line of HTTP request
278
	 * @return boolean|null Success
279
	 */
280
	protected function httpReadFirstline() {
281
		if (($l = $this->readline()) === null) {
282
			return null;
283
		}
284
		$e = explode(' ', $l);
285
		$u = isset($e[1]) ? parse_url($e[1]) : false;
286
		if ($u === false) {
287
			$this->badRequest();
288
			return false;
289
		}
290
		if (!isset($u['path'])) {
291
			$u['path'] = null;
292
		}
293
		if (isset($u['host'])) {
294
			$this->server['HTTP_HOST'] = $u['host'];
295
		}
296
		$srv                       = & $this->server;
297
		$srv['REQUEST_METHOD']     = $e[0];
298
		$srv['REQUEST_TIME']       = time();
299
		$srv['REQUEST_TIME_FLOAT'] = microtime(true);
300
		$srv['REQUEST_URI']        = $u['path'] . (isset($u['query']) ? '?' . $u['query'] : '');
301
		$srv['DOCUMENT_URI']       = $u['path'];
302
		$srv['PHP_SELF']           = $u['path'];
303
		$srv['QUERY_STRING']       = isset($u['query']) ? $u['query'] : null;
304
		$srv['SCRIPT_NAME']        = $srv['DOCUMENT_URI'] = isset($u['path']) ? $u['path'] : '/';
305
		$srv['SERVER_PROTOCOL']    = isset($e[2]) ? $e[2] : 'HTTP/1.1';
306
		$srv['REMOTE_ADDR']        = $this->addr;
307
		$srv['REMOTE_PORT']        = $this->port;
308
		return true;
309
	}
310
311
	/**
312
	 * Read headers line-by-line
313
	 * @return boolean|null Success
314
	 */
315
	protected function httpReadHeaders() {
316
		while (($l = $this->readLine()) !== null) {
317
			if ($l === '') {
318
				return true;
319
			}
320
			$e = explode(': ', $l);
321
			if (isset($e[1])) {
322
				$this->currentHeader                = 'HTTP_' . strtoupper(strtr($e[0], Generic::$htr));
323
				$this->server[$this->currentHeader] = $e[1];
324
			}
325 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...
326
				// multiline header continued
327
				$this->server[$this->currentHeader] .= $e[0];
328
			}
329
			else {
330
				// whatever client speaks is not HTTP anymore
331
				$this->badRequest();
332
				return false;
333
			}
334
		}
335
		return null;
336
	}
337
338
	/**
339
	 * Called when new data received.
340
	 * @return void
341
	 */
342
	protected function onRead() {
343 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...
344
			$d = $this->drainIfMatch("<policy-file-request/>\x00");
345
			if ($d === null) { // partially match
346
				return;
347
			}
348
			if ($d) {
349
				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 130 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
					$this->write($FP->policyData . "\x00");
351
				}
352
				$this->finish();
353
				return;
354
			}
355
			else {
356
				$this->policyReqNotFound = true;
357
			}
358
		}
359
		start:
360
		if ($this->finished) {
361
			return;
362
		}
363
		if ($this->state === self::STATE_STANDBY) {
364
			$this->state = self::STATE_FIRSTLINE;
365
		}
366
		if ($this->state === self::STATE_FIRSTLINE) {
367
			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...
368
				return;
369
			}
370
			$this->state = self::STATE_HEADERS;
371
		}
372
373 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...
374
			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...
375
				return;
376
			}
377
			if (!$this->httpProcessHeaders()) {
378
				$this->finish();
379
				return;
380
			}
381
			$this->state = self::STATE_CONTENT;
382
		}
383
		if ($this->state === self::STATE_CONTENT) {
384
			$this->state = self::STATE_PREHANDSHAKE;
385
		}
386
	}
387
388
	/**
389
	 * Process headers
390
	 * @return bool
391
	 */
392
	protected function httpProcessHeaders() {
393
		$this->state = self::STATE_PREHANDSHAKE;
394
		if (isset($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS'])) {
395
			$str              = strtolower($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS']);
396
			$str              = preg_replace($this->extensionsCleanRegex, '', $str);
397
			$this->extensions = explode(', ', $str);
398
		}
399
		if (!isset($this->server['HTTP_CONNECTION'])
400
				|| (!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 157 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...
401
				|| !isset($this->server['HTTP_UPGRADE'])
402
				|| (strtolower($this->server['HTTP_UPGRADE']) !== 'websocket') // Lowercase comparison iss important
403
		) {
404
			$this->finish();
405
			return false;
406
		}
407 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...
408
			Generic::parse_str(strtr($this->server['HTTP_COOKIE'], Generic::$hvaltr), $this->cookie);
409
		}
410
		if (isset($this->server['QUERY_STRING'])) {
411
			Generic::parse_str($this->server['QUERY_STRING'], $this->get);
412
		}
413
		// ----------------------------------------------------------
414
		// Protocol discovery, based on HTTP headers...
415
		// ----------------------------------------------------------
416
		if (isset($this->server['HTTP_SEC_WEBSOCKET_VERSION'])) { // HYBI
417
			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...
418
				$this->switchToProtocol('V13');
419
			}
420
			elseif ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] === '13') { // newest protocol
421
				$this->switchToProtocol('V13');
422
			}
423
			else {
424
				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 204 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...
425
				$this->finish();
426
				return false;
427
			}
428
		}
429 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...
430
			$this->switchToProtocol('VE');
431
		}
432
		else { // Defaulting to HIXIE (Safari5 and many non-browser clients...)
433
			$this->switchToProtocol('V0');
434
		}
435
		// ----------------------------------------------------------
436
		// End of protocol discovery
437
		// ----------------------------------------------------------
438
		return true;
439
	}
440
441
	protected function switchToProtocol($proto) {
442
		$class = '\\PHPDaemon\\Servers\\WebSocket\\Protocols\\' . $proto;
443
		$conn  = new $class(null, $this->pool);
444
		$this->pool->attach($conn);
445
		$conn->setFd($this->getFd(), $this->getBev());
446
		$this->unsetFd();
447
		$this->pool->detach($this);
448
		$conn->onInheritance($this);
449
	}
450
451 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...
452
		$this->server = $conn->server;
453
		$this->cookie = $conn->cookie;
454
		$this->get = $conn->get;
455
		$this->state = self::STATE_PREHANDSHAKE;
456
		$this->addr = $conn->addr;
457
		$this->onRead();
458
	}
459
460
461
	/**
462
	 * Send HTTP-status
463
	 * @throws RequestHeadersAlreadySent
464
	 * @param  integer $code Code
465
	 * @return boolean       Success
466
	 */
467
	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...
468
		return false;
469
	}
470
471
	/**
472
	 * Send the header
473
	 * @param  string  $s       Header. Example: 'Location: http://php.net/'
474
	 * @param  boolean $replace Optional. Replace?
475
	 * @param  boolean $code    Optional. HTTP response code
476
	 * @throws \PHPDaemon\Request\RequestHeadersAlreadySent
477
	 * @return boolean          Success
478
	 */
479 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...
480
		if ($code) {
481
			$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...
482
		}
483
484
		if ($this->headers_sent) {
485
			throw new RequestHeadersAlreadySent;
486
		}
487
		$s = strtr($s, "\r\n", '  ');
488
489
		$e = explode(':', $s, 2);
490
491
		if (!isset($e[1])) {
492
			$e[0] = 'STATUS';
493
494
			if (strncmp($s, 'HTTP/', 5) === 0) {
495
				$s = substr($s, 9);
496
			}
497
		}
498
499
		$k = strtr(strtoupper($e[0]), Generic::$htr);
500
501
		if ($k === 'CONTENT_TYPE') {
502
			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...
503
			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...
504
				$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...
505
506
				$s = $e[0] . ': ';
507
				$i = 0;
508
				foreach ($ctype as $k => $v) {
509
					$s .= ($i > 0 ? '; ' : '') . $k . ($v !== '' ? '=' . $v : '');
510
					++$i;
511
				}
512
			}
513
		}
514
		if ($k === 'SET_COOKIE') {
515
			$k .= '_' . ++$this->cookieNum;
516
		}
517
		elseif (!$replace && isset($this->headers[$k])) {
518
			return false;
519
		}
520
521
		$this->headers[$k] = $s;
522
523
		if ($k === 'CONTENT_LENGTH') {
524
			$this->contentLength = (int)$e[1];
525
		}
526
		elseif ($k === 'LOCATION') {
527
			$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...
528
		}
529
530
		if (Daemon::$compatMode) {
531
			is_callable('header_native') ? header_native($s) : header($s);
532
		}
533
534
		return true;
535
	}
536
}
537