Completed
Push — bugfix/on-before-handshake ( b4f7f7 )
by
unknown
05:04
created

Connection::handshakeAfter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 13

Duplication

Lines 5
Ratio 29.41 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 5
loc 17
rs 9.4286
cc 3
eloc 13
nc 3
nop 1
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
		$this->route->onSleep();
193
		return true;
194
	}
195
196
	/**
197
	 * Called when the worker is going to shutdown.
198
	 * @return boolean Ready to shutdown ?
199
	 */
200
	public function gracefulShutdown() {
201
		if ((!$this->route) || $this->route->gracefulShutdown()) {
202
			$this->finish();
203
			return true;
204
		}
205
		return FALSE;
206
	}
207
208
209
	/**
210
	 * Called when we're going to handshake.
211
	 * @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...
212
	 */
213
	public function handshake() {
214
		$this->route = $this->pool->getRoute($this->server['DOCUMENT_URI'], $this);
215 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...
216
			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...
217
			$this->finish();
218
			return false;
219
		}
220
221
		if (method_exists($this->route, 'onBeforeHandshake')) {
222
			$this->route->onWakeup();
223
			$ret = $this->route->onBeforeHandshake(function() {
224
				$extraHeaders = '';
225
				foreach ($this->headers as $k => $line) {
226
					if ($k !== 'STATUS') {
227
						$extraHeaders .= $line . "\r\n";
228
					}
229
				}
230
231
				$this->handshakeAfter($extraHeaders);
232
			});
233
			$this->route->onSleep();
234
			if ($ret !== false) {
235
				return;
236
			}
237
		}
238
239
		$this->handshakeAfter();
240
	}
241
242
	protected function handshakeAfter($extraHeaders = '') {
243 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...
244
			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...
245
			$this->finish();
246
			return false;
247
		}
248
249
		$this->handshaked = true;
250
		$this->headers_sent = true;
251
		$this->state = static::STATE_HANDSHAKED;
252
		if (is_callable([$this->route, 'onHandshake'])) {
253
			$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...
254
			$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...
255
			$this->route->onSleep();
0 ignored issues
show
Bug introduced by
The method onSleep 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...
256
		}
257
		return true;
258
	}
259
	
260
	/**
261
	 * Send Bad request
262
	 * @return void
263
	 */
264
	public function badRequest() {
265
		$this->state = self::STATE_STANDBY;
266
		$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...
267
		$this->finish();
268
	}
269
270
	/**
271
	 * Read first line of HTTP request
272
	 * @return boolean|null Success
273
	 */
274
	protected function httpReadFirstline() {
275
		if (($l = $this->readline()) === null) {
276
			return null;
277
		}
278
		$e = explode(' ', $l);
279
		$u = isset($e[1]) ? parse_url($e[1]) : false;
280
		if ($u === false) {
281
			$this->badRequest();
282
			return false;
283
		}
284
		if (!isset($u['path'])) {
285
			$u['path'] = null;
286
		}
287
		if (isset($u['host'])) {
288
			$this->server['HTTP_HOST'] = $u['host'];
289
		}
290
		$srv                       = & $this->server;
291
		$srv['REQUEST_METHOD']     = $e[0];
292
		$srv['REQUEST_TIME']       = time();
293
		$srv['REQUEST_TIME_FLOAT'] = microtime(true);
294
		$srv['REQUEST_URI']        = $u['path'] . (isset($u['query']) ? '?' . $u['query'] : '');
295
		$srv['DOCUMENT_URI']       = $u['path'];
296
		$srv['PHP_SELF']           = $u['path'];
297
		$srv['QUERY_STRING']       = isset($u['query']) ? $u['query'] : null;
298
		$srv['SCRIPT_NAME']        = $srv['DOCUMENT_URI'] = isset($u['path']) ? $u['path'] : '/';
299
		$srv['SERVER_PROTOCOL']    = isset($e[2]) ? $e[2] : 'HTTP/1.1';
300
		$srv['REMOTE_ADDR']        = $this->addr;
301
		$srv['REMOTE_PORT']        = $this->port;
302
		return true;
303
	}
304
305
	/**
306
	 * Read headers line-by-line
307
	 * @return boolean|null Success
308
	 */
309
	protected function httpReadHeaders() {
310
		while (($l = $this->readLine()) !== null) {
311
			if ($l === '') {
312
				return true;
313
			}
314
			$e = explode(': ', $l);
315
			if (isset($e[1])) {
316
				$this->currentHeader                = 'HTTP_' . strtoupper(strtr($e[0], Generic::$htr));
317
				$this->server[$this->currentHeader] = $e[1];
318
			}
319 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...
320
				// multiline header continued
321
				$this->server[$this->currentHeader] .= $e[0];
322
			}
323
			else {
324
				// whatever client speaks is not HTTP anymore
325
				$this->badRequest();
326
				return false;
327
			}
328
		}
329
		return null;
330
	}
331
332
	/**
333
	 * Called when new data received.
334
	 * @return void
335
	 */
336
	protected function onRead() {
337 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...
338
			$d = $this->drainIfMatch("<policy-file-request/>\x00");
339
			if ($d === null) { // partially match
340
				return;
341
			}
342
			if ($d) {
343
				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...
344
					$this->write($FP->policyData . "\x00");
345
				}
346
				$this->finish();
347
				return;
348
			}
349
			else {
350
				$this->policyReqNotFound = true;
351
			}
352
		}
353
		start:
354
		if ($this->finished) {
355
			return;
356
		}
357
		if ($this->state === self::STATE_STANDBY) {
358
			$this->state = self::STATE_FIRSTLINE;
359
		}
360
		if ($this->state === self::STATE_FIRSTLINE) {
361
			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...
362
				return;
363
			}
364
			$this->state = self::STATE_HEADERS;
365
		}
366
367 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...
368
			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...
369
				return;
370
			}
371
			if (!$this->httpProcessHeaders()) {
372
				$this->finish();
373
				return;
374
			}
375
			$this->state = self::STATE_CONTENT;
376
		}
377
		if ($this->state === self::STATE_CONTENT) {
378
			$this->state = self::STATE_PREHANDSHAKE;
379
		}
380
	}
381
382
	/**
383
	 * Process headers
384
	 * @return bool
385
	 */
386
	protected function httpProcessHeaders() {
387
		$this->state = self::STATE_PREHANDSHAKE;
388
		if (isset($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS'])) {
389
			$str              = strtolower($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS']);
390
			$str              = preg_replace($this->extensionsCleanRegex, '', $str);
391
			$this->extensions = explode(', ', $str);
392
		}
393
		if (!isset($this->server['HTTP_CONNECTION'])
394
				|| (!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...
395
				|| !isset($this->server['HTTP_UPGRADE'])
396
				|| (strtolower($this->server['HTTP_UPGRADE']) !== 'websocket') // Lowercase compare important
397
		) {
398
			$this->finish();
399
			return false;
400
		}
401 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...
402
			Generic::parse_str(strtr($this->server['HTTP_COOKIE'], Generic::$hvaltr), $this->cookie);
403
		}
404
		if (isset($this->server['QUERY_STRING'])) {
405
			Generic::parse_str($this->server['QUERY_STRING'], $this->get);
406
		}
407
		// ----------------------------------------------------------
408
		// Protocol discovery, based on HTTP headers...
409
		// ----------------------------------------------------------
410
		if (isset($this->server['HTTP_SEC_WEBSOCKET_VERSION'])) { // HYBI
411
			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...
412
				$this->switchToProtocol('V13');
413
			}
414
			elseif ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] === '13') { // newest protocol
415
				$this->switchToProtocol('V13');
416
			}
417
			else {
418
				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...
419
				$this->finish();
420
				return false;
421
			}
422
		}
423 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...
424
			$this->switchToProtocol('VE');
425
		}
426
		else { // Defaulting to HIXIE (Safari5 and many non-browser clients...)
427
			$this->switchToProtocol('V0');
428
		}
429
		// ----------------------------------------------------------
430
		// End of protocol discovery
431
		// ----------------------------------------------------------
432
		return true;
433
	}
434
435
	protected function switchToProtocol($proto) {
436
		$class = '\\PHPDaemon\\Servers\\WebSocket\\Protocols\\' . $proto;
437
		$conn  = new $class(null, $this->pool);
438
		$this->pool->attach($conn);
439
		$conn->setFd($this->getFd(), $this->getBev());
440
		$this->unsetFd();
441
		$this->pool->detach($this);
442
		$conn->onInheritance($this);
443
	}
444
445 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...
446
		$this->server = $conn->server;
447
		$this->cookie = $conn->cookie;
448
		$this->get = $conn->get;
449
		$this->state = self::STATE_PREHANDSHAKE;
450
		$this->addr = $conn->addr;
451
		$this->onRead();
452
	}
453
454
455
	/**
456
	 * Send HTTP-status
457
	 * @throws RequestHeadersAlreadySent
458
	 * @param  integer $code Code
459
	 * @return boolean       Success
460
	 */
461
	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...
462
		return false;
463
	}
464
465
	/**
466
	 * Send the header
467
	 * @param  string  $s       Header. Example: 'Location: http://php.net/'
468
	 * @param  boolean $replace Optional. Replace?
469
	 * @param  boolean $code    Optional. HTTP response code
470
	 * @throws \PHPDaemon\Request\RequestHeadersAlreadySent
471
	 * @return boolean          Success
472
	 */
473 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...
474
		if ($code) {
475
			$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...
476
		}
477
478
		if ($this->headers_sent) {
479
			throw new RequestHeadersAlreadySent;
480
		}
481
		$s = strtr($s, "\r\n", '  ');
482
483
		$e = explode(':', $s, 2);
484
485
		if (!isset($e[1])) {
486
			$e[0] = 'STATUS';
487
488
			if (strncmp($s, 'HTTP/', 5) === 0) {
489
				$s = substr($s, 9);
490
			}
491
		}
492
493
		$k = strtr(strtoupper($e[0]), Generic::$htr);
494
495
		if ($k === 'CONTENT_TYPE') {
496
			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...
497
			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...
498
				$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...
499
500
				$s = $e[0] . ': ';
501
				$i = 0;
502
				foreach ($ctype as $k => $v) {
503
					$s .= ($i > 0 ? '; ' : '') . $k . ($v !== '' ? '=' . $v : '');
504
					++$i;
505
				}
506
			}
507
		}
508
		if ($k === 'SET_COOKIE') {
509
			$k .= '_' . ++$this->cookieNum;
510
		}
511
		elseif (!$replace && isset($this->headers[$k])) {
512
			return false;
513
		}
514
515
		$this->headers[$k] = $s;
516
517
		if ($k === 'CONTENT_LENGTH') {
518
			$this->contentLength = (int)$e[1];
519
		}
520
		elseif ($k === 'LOCATION') {
521
			$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...
522
		}
523
524
		if (Daemon::$compatMode) {
525
			is_callable('header_native') ? header_native($s) : header($s);
526
		}
527
528
		return true;
529
	}
530
}
531