Completed
Push — master ( c531bb...c55d2b )
by Vasily
05:01 queued 01:17
created

Connection::write()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 3
rs 10
c 1
b 0
f 0
cc 1
eloc 2
nc 1
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) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

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

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

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

Loading history...
Unused Code introduced by
The parameter $cb 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...
147
		return false;
148
	}
149
150
	/**
151
	 * Event of Connection.
152
	 * @return void
153
	 */
154
	public function onFinish() {
155
156
		$this->sendFrame('', 'CONNCLOSE');
0 ignored issues
show
Unused Code introduced by
The call to the method PHPDaemon\Servers\WebSoc...Connection::sendFrame() 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...
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 null|boolean?

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

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