Completed
Push — master ( 2db126...359446 )
by Vasily
04:42
created

IOStream   D

Complexity

Total Complexity 149

Size/Duplication

Total Lines 894
Duplicated Lines 7.49 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 149
c 6
b 0
f 0
lcom 1
cbo 6
dl 67
loc 894
rs 4

47 Methods

Rating   Name   Duplication   Size   Complexity  
A isFreed() 0 3 1
A isFinished() 0 3 1
A getBev() 0 3 1
A getFd() 0 3 1
A setContext() 0 4 1
A setTimeout() 0 3 1
A setPriority() 0 4 1
C __construct() 0 31 8
B __get() 0 10 5
C setFd() 4 65 19
A setTimeouts() 0 7 2
A init() 0 2 1
A readLine() 0 6 3
A drain() 0 3 1
D drainIfMatch() 0 25 9
A lookExact() 9 9 3
A prependInput() 0 6 2
A prependOutput() 0 6 2
A look() 9 9 3
A substr() 0 6 2
A search() 0 3 1
A readExact() 0 9 3
A getInputLength() 0 3 1
A gracefulShutdown() 0 4 1
A freezeInput() 0 6 2
A unfreezeInput() 0 6 2
A freezeOutput() 0 6 2
A unfreezeOutput() 0 6 2
A onWrite() 0 2 1
B write() 19 19 6
B writeln() 16 16 5
A finish() 0 11 3
A onFinish() 0 2 1
A close() 0 13 4
A unsetFd() 0 4 1
A log() 0 3 1
B onReadEv() 0 17 5
A onRead() 0 2 1
A onReady() 0 2 1
A onWriteOnce() 0 7 2
C onWriteEv() 0 43 11
D onStateEv() 0 28 10
A moveToBuffer() 0 6 2
A writeFromBuffer() 0 7 2
A read() 0 13 4
A readUnlimited() 10 10 3
B setWatermark() 0 12 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like IOStream often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IOStream, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace PHPDaemon\Network;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\FS\File;
7
use PHPDaemon\Structures\StackCallbacks;
8
9
/**
10
 * IOStream
11
 * @package PHPDaemon\Network
12
 * @author  Vasily Zorin <[email protected]>
13
 */
14
abstract class IOStream {
15
	use \PHPDaemon\Traits\ClassWatchdog;
16
	use \PHPDaemon\Traits\StaticObjectWatchdog;
17
	use \PHPDaemon\Traits\EventHandlers;
18
19
	/**
20
	 * @var object Associated pool
21
	 */
22
	public $pool;
23
24
	/**
25
	 * @var string EOL
26
	 */
27
	protected $EOL = "\n";
28
29
	/**
30
	 * @var integer EOLS_* switch
31
	 */
32
	protected $EOLS;
33
34
	/**
35
	 * @var object EventBufferEvent
36
	 */
37
	protected $bev;
38
39
	/**
40
	 * @var resource File descriptor
41
	 */
42
	protected $fd;
43
44
	/**
45
	 * @var boolean Finished?
46
	 */
47
	protected $finished = false;
48
49
	/**
50
	 * @var boolean Ready?
51
	 */
52
	protected $ready = false;
53
54
	/**
55
	 * @var boolean Writing?
56
	 */
57
	protected $writing = true;
58
59
	/**
60
	 * @var integer Default low mark. Minimum number of bytes in buffer
61
	 */
62
	protected $lowMark = 1;
63
64
	/**
65
	 * @var integer Default high mark. Maximum number of bytes in buffer
66
	 */
67
	protected $highMark = 0xFFFF; // initial value of the maximum amout of bytes in buffer
68
69
	/**
70
	 * @var integer Priority
71
	 */
72
	protected $priority;
73
74
	/**
75
	 * @var boolean Initialized?
76
	 */
77
	protected $inited = false;
78
79
	/**
80
	 * @var integer Current state
81
	 */
82
	protected $state = 0; // stream state of the connection (application protocol level)
83
84
	/**
85
	 * Alias of STATE_STANDBY
86
	 */
87
	const STATE_ROOT = 0;
88
89
	/**
90
	 * Standby state (default state)
91
	 */
92
	const STATE_STANDBY = 0;
93
94
	/**
95
	 * @var object Stack of callbacks called when writing is done
96
	 */
97
	protected $onWriteOnce;
98
99
	/**
100
	 * @var integer Timeout
101
	 */
102
	protected $timeout = null;
103
104
	/**
105
	 * @var string URL
106
	 */
107
	protected $url;
108
109
	/**
110
	 * @var boolean Alive?
111
	 */
112
	protected $alive = false;
113
114
	/**
115
	 * @var boolean Is bevConnect used?
116
	 */
117
	protected $bevConnect = false;
118
119
	/**
120
	 * @var boolean Should we can onReadEv() in next onWriteEv()?
121
	 */
122
	protected $wRead = false;
123
124
	/**
125
	 * @var boolean Freed?
126
	 */
127
	protected $freed = false;
128
129
	/**
130
	 * @var object Context
131
	 */
132
	protected $ctx;
133
134
	/**
135
	 * @var object Context name
136
	 */
137
	protected $ctxname;
138
139
	/**
140
	 * @var integer Defines context-related flag
141
	 */
142
	protected $ctxMode;
143
144
	/**
145
	 * @var boolean SSL?
146
	 */
147
	protected $ssl = false;
148
149
	/**
150
	 * @var float Read timeout
151
	 */
152
	protected $timeoutRead;
153
154
	/**
155
	 * @var float Write timeout
156
	 */
157
	protected $timeoutWrite;
158
159
	/**
160
	 * IOStream constructor
161
	 * @param resource $fd   File descriptor. Optional
0 ignored issues
show
Documentation introduced by
Should the type for parameter $fd not be resource|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...
162
	 * @param object   $pool Pool. Optional
0 ignored issues
show
Documentation introduced by
Should the type for parameter $pool not be object|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...
163
	 */
164
	public function __construct($fd = null, $pool = null) {
165
		if ($pool) {
166
			$this->pool = $pool;
167
			$this->pool->attach($this);
168
			if (isset($this->pool->config->timeout->value)) {
169
				$this->timeout = $this->pool->config->timeout->value;
170
			}
171
			if (isset($this->pool->config->timeoutread->value)) {
172
				$this->timeoutRead = $this->pool->config->timeoutread->value;
173
			}
174
			if (isset($this->pool->config->timeoutwrite->value)) {
175
				$this->timeoutWrite = $this->pool->config->timeoutwrite->value;
176
			}
177
		}
178
179
		if ($fd !== null) {
180
			$this->setFd($fd);
181
		}
182
183
		if ($this->EOL === "\n") {
184
			$this->EOLS = \EventBuffer::EOL_LF;
185
		}
186
		elseif ($this->EOL === "\r\n") {
187
			$this->EOLS = \EventBuffer::EOL_CRLF;
188
		}
189
		else {
190
			$this->EOLS = \EventBuffer::EOL_ANY;
191
		}
192
193
		$this->onWriteOnce = new StackCallbacks;
194
	}
195
196
	/**
197
	 * Getter
198
	 * @param  string $name Name
199
	 * @return mixed
200
	 */
201
	public function __get($name) {
202
		if (   $name === 'finished'
203
			|| $name === 'alive'
204
			|| $name === 'freed'
205
			|| $name === 'url'
206
		) {
207
			return $this->{$name};
208
		}
209
		return NULL;
210
	}
211
212
213
	/**
214
	 * Freed?
215
	 * @return boolean
216
	 */
217
	public function isFreed() {
218
		return $this->freed;
219
	}
220
221
	/**
222
	 * Finished?
223
	 * @return boolean
224
	 */
225
	public function isFinished() {
226
		return $this->finished;
227
	}
228
229
	/**
230
	 * Get EventBufferEvent
231
	 * @return EventBufferEvent
232
	 */
233
	public function getBev() {
234
		return $this->bev;
235
	}
236
237
	/**
238
	 * Get file descriptor
239
	 * @return resource File descriptor
240
	 */
241
	public function getFd() {
242
		return $this->fd;
243
	}
244
245
	/**
246
	 * Sets context mode
247
	 * @param  object  $ctx  Context
248
	 * @param  integer $mode Mode
249
	 * @return void
250
	 */
251
252
	public function setContext($ctx, $mode) {
253
		$this->ctx     = $ctx;
254
		$this->ctxMode = $mode;
255
	}
256
257
	/**
258
	 * Sets fd
259
	 * @param  resource $fd  File descriptor
260
	 * @param  object   $bev EventBufferEvent
0 ignored issues
show
Documentation introduced by
Should the type for parameter $bev not be object|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...
261
	 * @return void
262
	 */
263
	public function setFd($fd, $bev = null) {
264
		$this->fd = $fd;
265
		if ($this->fd === false) {
266
			$this->finish();
267
			return;
268
		}
269
		if ($bev !== null) {
270
			$this->bev = $bev;
271
			$this->bev->setCallbacks([$this, 'onReadEv'], [$this, 'onWriteEv'], [$this, 'onStateEv']);
272
			if (!$this->bev) {
273
				return;
274
			}
275
			$this->ready = true;
276
			$this->alive = true;
277
		}
278
		else {
279
			$flags = !is_resource($this->fd) ? \EventBufferEvent::OPT_CLOSE_ON_FREE : 0;
280
			$flags |= \EventBufferEvent::OPT_DEFER_CALLBACKS; /* buggy option */
281
			if ($this->ctx) {
282
				if ($this->ctx instanceof \EventSslContext) {
0 ignored issues
show
Bug introduced by
The class EventSslContext does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
283
					$this->bev = \EventBufferEvent::sslSocket(Daemon::$process->eventBase, $this->fd, $this->ctx, $this->ctxMode, $flags);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

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

Loading history...
284
					if ($this->bev) {
285
						$this->bev->setCallbacks([$this, 'onReadEv'], [$this, 'onWriteEv'], [$this, 'onStateEv']);
286
					}
287
					$this->ssl = true;
288
				}
289
				else {
290
					$this->log('unsupported type of context: ' . ($this->ctx ? get_class($this->ctx) : 'undefined'));
291
					return;
292
				}
293
			}
294
			else {
295
				$this->bev = new \EventBufferEvent(Daemon::$process->eventBase, $this->fd, $flags, [$this, 'onReadEv'], [$this, 'onWriteEv'], [$this, 'onStateEv']);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 152 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...
296
			}
297
			if (!$this->bev) {
298
				return;
299
			}
300
		}
301
		if ($this->priority !== null) {
302
			$this->bev->priority = $this->priority;
303
		}
304
		$this->setTimeouts($this->timeoutRead !== null ? $this->timeoutRead : $this->timeout,
305
							$this->timeoutWrite!== null ? $this->timeoutWrite : $this->timeout);
306
		if ($this->bevConnect && ($this->fd === null)) {
307
			//$this->bev->connectHost(Daemon::$process->dnsBase, $this->hostReal, $this->port);
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% 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...
308
			$this->bev->connect($this->addr);
0 ignored issues
show
Documentation introduced by
The property addr does not exist on object<PHPDaemon\Network\IOStream>. 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...
309
		}
310
		if (!$this->bev) {
311
			$this->finish();
312
			return;
313
		}
314 View Code Duplication
		if (!$this->bev->enable(\Event::READ | \Event::WRITE | \Event::TIMEOUT | \Event::PERSIST)) {
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...
315
			$this->finish();
316
			return;
317
		}
318
		$this->bev->setWatermark(\Event::READ, $this->lowMark, $this->highMark);
319
		init:
320
		if ($this->keepalive) {
0 ignored issues
show
Documentation introduced by
The property keepalive does not exist on object<PHPDaemon\Network\IOStream>. 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...
321
			$this->setKeepalive(true);
0 ignored issues
show
Documentation Bug introduced by
The method setKeepalive does not exist on object<PHPDaemon\Network\IOStream>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
322
		}
323
		if (!$this->inited) {
324
			$this->inited = true;
325
			$this->init();
326
		}
327
	}
328
329
	/**
330
	 * Set timeout
331
	 * @param  integer $rw Timeout
332
	 * @return void
333
	 */
334
	public function setTimeout($rw) {
335
		$this->setTimeouts($rw, $rw);
336
	}
337
338
	/**
339
	 * Set timeouts
340
	 * @param  integer $read  Read timeout in seconds
341
	 * @param  integer $write Write timeout in seconds
342
	 * @return void
343
	 */
344
	public function setTimeouts($read, $write) {
345
		$this->timeoutRead  = $read;
0 ignored issues
show
Documentation Bug introduced by
The property $timeoutRead was declared of type double, but $read is of type integer. Maybe add a type cast?

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

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

$answer = 42;

$correct = false;

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

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

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

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
347
		if ($this->bev) {
348
			$this->bev->setTimeouts($this->timeoutRead, $this->timeoutWrite);
349
		}
350
	}
351
352
	/**
353
	 * Sets priority
354
	 * @param  integer $p Priority
355
	 * @return void
356
	 */
357
	public function setPriority($p) {
358
		$this->priority      = $p;
359
		$this->bev->priority = $p;
360
	}
361
362
	/**
363
	 * Sets watermark
364
	 * @param  integer|null $low  Low
365
	 * @param  integer|null $high High
366
	 * @return void
367
	 */
368
	public function setWatermark($low = null, $high = null) {
369
		if ($low !== null) {
370
			$this->lowMark = $low;
371
		}
372
		if ($high !== null) {
373
			$this->highMark = $high;
374
		}
375
		if ($this->highMark !== null && $this->lowMark > $this->highMark) {
376
			$this->highMark = $this->lowMark;
377
		}
378
		$this->bev->setWatermark(\Event::READ, $this->lowMark, $this->highMark);
379
	}
380
381
	/**
382
	 * Called when the session constructed
383
	 * @return void
384
	 */
385
	protected function init() {
386
	}
387
388
	/**
389
	 * Reads line from buffer
390
	 * @param  integer     $eol EOLS_*
0 ignored issues
show
Documentation introduced by
Should the type for parameter $eol not be integer|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...
391
	 * @return string|null
392
	 */
393
	public function readLine($eol = null) {
394
		if (!isset($this->bev)) {
395
			return null;
396
		}
397
		return $this->bev->input->readLine($eol ? : $this->EOLS);
398
	}
399
400
	/**
401
	 * Drains buffer
402
	 * @param  integer $n Numbers of bytes to drain
403
	 * @return boolean    Success
404
	 */
405
	public function drain($n) {
406
		return $this->bev->input->drain($n);
407
	}
408
409
	/**
410
	 * Drains buffer it matches the string
411
	 * @param  string       $str Data
412
	 * @return boolean|null      Success
413
	 */
414
	public function drainIfMatch($str) {
415
		if (!isset($this->bev)) {
416
			return false;
417
		}
418
		$in = $this->bev->input;
419
		$l  = strlen($str);
420
		$ll = $in->length;
421
		if ($ll === 0) {
422
			return $l === 0 ? true : null;
423
		}
424
		if ($ll < $l) {
425
			return $in->search(substr($str, 0, $ll)) === 0 ? null : false;
426
		}
427
		if ($ll === $l) {
428
			if ($in->search($str) === 0) {
429
				$in->drain($l);
430
				return true;
431
			}
432
		}
433
		elseif ($in->search($str, 0, $l) === 0) {
434
			$in->drain($l);
435
			return true;
436
		}
437
		return false;
438
	}
439
440
	/**
441
	 * Reads exact $n bytes of buffer without draining
442
	 * @param  integer $n Number of bytes to read
443
	 * @param  integer $o Offset
444
	 * @return string|false
445
	 */
446 View Code Duplication
	public function lookExact($n, $o = 0) {
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...
447
		if (!isset($this->bev)) {
448
			return false;
449
		}
450
		if ($o + $n > $this->bev->input->length) {
451
			return false;
452
		}
453
		return $this->bev->input->substr($o, $n);
454
	}
455
456
	/**
457
	 * Prepends data to input buffer
458
	 * @param  string  $str Data
459
	 * @return boolean      Success
460
	 */
461
	public function prependInput($str) {
462
		if (!isset($this->bev)) {
463
			return false;
464
		}
465
		return $this->bev->input->prepend($str);
466
	}
467
468
	/**
469
	 * Prepends data to output buffer
470
	 * @param  string  $str Data
471
	 * @return boolean      Success
472
	 */
473
	public function prependOutput($str) {
474
		if (!isset($this->bev)) {
475
			return false;
476
		}
477
		return $this->bev->output->prepend($str);
478
	}
479
480
	/**
481
	 * Read from buffer without draining
482
	 * @param integer $n Number of bytes to read
483
	 * @param integer $o Offset
484
	 * @return string|false
485
	 */
486 View Code Duplication
	public function look($n, $o = 0) {
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...
487
		if (!isset($this->bev)) {
488
			return false;
489
		}
490
		if ($this->bev->input->length <= $o) {
491
			return '';
492
		}
493
		return $this->bev->input->substr($o, $n);
494
	}
495
496
	/**
497
	 * Read from buffer without draining
498
	 * @param  integer $o Offset
499
	 * @param  integer $n Number of bytes to read
500
	 * @return string|false
501
	 */
502
	public function substr($o, $n = -1) {
503
		if (!isset($this->bev)) {
504
			return false;
505
		}
506
		return $this->bev->input->substr($o, $n);
507
	}
508
509
	/**
510
	 * Searches first occurence of the string in input buffer
511
	 * @param  string  $what  Needle
512
	 * @param  integer $start Offset start
513
	 * @param  integer $end   Offset end
514
	 * @return integer        Position
515
	 */
516
	public function search($what, $start = 0, $end = -1) {
517
		return $this->bev->input->search($what, $start, $end);
518
	}
519
520
	/**
521
	 * Reads exact $n bytes from buffer
522
	 * @param  integer      $n Number of bytes to read
523
	 * @return string|false
524
	 */
525
	public function readExact($n) {
526
		if ($n === 0) {
527
			return '';
528
		}
529
		if ($this->bev->input->length < $n) {
530
			return false;
531
		}
532
		return $this->read($n);
533
	}
534
535
	/**
536
	 * Returns length of input buffer
537
	 * @return integer
538
	 */
539
	public function getInputLength() {
540
		return $this->bev->input->length;
541
	}
542
543
	/**
544
	 * Called when the worker is going to shutdown
545
	 * @return boolean Ready to shutdown?
546
	 */
547
	public function gracefulShutdown() {
548
		$this->finish();
549
		return true;
550
	}
551
552
	/**
553
	 * Freeze input
554
	 * @param  boolean $at_front At front. Default is true. If the front of a buffer is frozen, operations that drain data from the front of the buffer, or that prepend data to the buffer, will fail until it is unfrozen. If the back a buffer is frozen, operations that append data from the buffer will fail until it is unfrozen
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 324 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...
555
	 * @return boolean           Success
556
	 */
557
	public function freezeInput($at_front = true) {
558
		if (isset($this->bev)) {
559
			return $this->bev->input->freeze($at_front);
560
		}
561
		return false;
562
	}
563
564
	/**
565
	 * Unfreeze input
566
	 * @param  boolean $at_front At front. Default is true. If the front of a buffer is frozen, operations that drain data from the front of the buffer, or that prepend data to the buffer, will fail until it is unfrozen. If the back a buffer is frozen, operations that append data from the buffer will fail until it is unfrozen
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 324 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...
567
	 * @return boolean           Success
568
	 */
569
	public function unfreezeInput($at_front = true) {
570
		if (isset($this->bev)) {
571
			return $this->bev->input->unfreeze($at_front);
572
		}
573
		return false;
574
	}
575
576
	/**
577
	 * Freeze output
578
	 * @param  boolean $at_front At front. Default is true. If the front of a buffer is frozen, operations that drain data from the front of the buffer, or that prepend data to the buffer, will fail until it is unfrozen. If the back a buffer is frozen, operations that append data from the buffer will fail until it is unfrozen
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 324 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...
579
	 * @return boolean           Success
580
	 */
581
	public function freezeOutput($at_front = true) {
582
		if (isset($this->bev)) {
583
			return $this->bev->output->unfreeze($at_front);
584
		}
585
		return false;
586
	}
587
588
	/**
589
	 * Unfreeze output
590
	 * @param  boolean $at_front At front. Default is true. If the front of a buffer is frozen, operations that drain data from the front of the buffer, or that prepend data to the buffer, will fail until it is unfrozen. If the back a buffer is frozen, operations that append data from the buffer will fail until it is unfrozen
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 324 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...
591
	 * @return boolean           Success
592
	 */
593
	public function unfreezeOutput($at_front = true) {
594
		if (isset($this->bev)) {
595
			return $this->bev->output->unfreeze($at_front);
596
		}
597
		return false;
598
	}
599
600
	/**
601
	 * Called when the connection is ready to accept new data
602
	 * @return void
603
	 */
604
	public function onWrite() {
605
	}
606
607
	/**
608
	 * Send data to the connection. Note that it just writes to buffer that flushes at every baseloop
609
	 * @param  string  $data Data to send
610
	 * @return boolean       Success
611
	 */
612 View Code Duplication
	public function write($data) {
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...
613
		if (!$this->alive) {
614
			Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
615
			return false;
616
		}
617
		if (!isset($this->bev)) {
618
			return false;
619
		}
620
		if (!strlen($data)) {
621
			return true;
622
		}
623
		$this->writing   = true;
624
		Daemon::$noError = true;
625
		if (!$this->bev->write($data) || !Daemon::$noError) {
626
			$this->close();
627
			return false;
628
		}
629
		return true;
630
	}
631
632
	/**
633
	 * Send data and appending \n to connection. Note that it just writes to buffer flushed at every baseloop
634
	 * @param  string  $data Data to send
635
	 * @return boolean       Success
636
	 */
637 View Code Duplication
	public function writeln($data) {
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...
638
		if (!$this->alive) {
639
			Daemon::log('Attempt to write to dead IOStream (' . get_class($this) . ')');
640
			return false;
641
		}
642
		if (!isset($this->bev)) {
643
			return false;
644
		}
645
		if (!strlen($data) && !strlen($this->EOL)) {
646
			return true;
647
		}
648
		$this->writing = true;
649
		$this->bev->write($data);
650
		$this->bev->write($this->EOL);
651
		return true;
652
	}
653
654
	/**
655
	 * Finish the session. You should not worry about buffers, they are going to be flushed properly
656
	 * @return void
657
	 */
658
	public function finish() {
659
		if ($this->finished) {
660
			return;
661
		}
662
		$this->finished = true;
663
		Daemon::$process->eventBase->stop();
664
		$this->onFinish();
665
		if (!$this->writing) {
666
			$this->close();
667
		}
668
	}
669
670
	/**
671
	 * Called when the session finished
672
	 * @return void
673
	 */
674
	protected function onFinish() {
675
	}
676
677
	/**
678
	 * Close the connection
679
	 * @return void
680
	 */
681
	public function close() {
682
		if (!$this->freed) {
683
			$this->freed = true;
684
			if (isset($this->bev)) {
685
				$this->bev->free();
686
			}
687
			$this->bev = null;
688
			//Daemon::$process->eventBase->stop();
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
689
		}
690
		if ($this->pool) {
691
			$this->pool->detach($this);
692
		}
693
	}
694
695
	/**
696
	 * Unsets pointers of associated EventBufferEvent and File descriptr
697
	 * @return void
698
	 */
699
	public function unsetFd() {
700
		$this->bev = null;
701
		$this->fd  = null;
702
	}
703
704
	/**
705
	 * Send message to log
706
	 * @param  string $m Message
707
	 * @return void
708
	 */
709
	protected function log($m) {
710
		Daemon::log(get_class($this) . ': ' . $m);
711
	}
712
713
	/**
714
	 * Called when the connection has got new data
715
	 * @param  object $bev EventBufferEvent
716
	 * @return void
717
	 */
718
	public function onReadEv($bev) {
0 ignored issues
show
Unused Code introduced by
The parameter $bev 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...
719
		if (Daemon::$config->logevents->value) {
720
			$this->log(' onReadEv called');
721
		}
722
		if (!$this->ready) {
723
			$this->wRead = true;
724
			return;
725
		}
726
		if ($this->finished) {
727
			return;
728
		}
729
		try {
730
			$this->onRead();
731
		} catch (\Exception $e) {
732
			Daemon::uncaughtExceptionHandler($e);
733
		}
734
	}
735
736
	/**
737
	 * Called when new data received
738
	 * @return void
739
	 */
740
	protected function onRead() {
741
	}
742
743
	/**
744
	 * Called when the stream is handshaked (at low-level), and peer is ready to recv. data
745
	 * @return void
746
	 */
747
	protected function onReady() {
748
	}
749
750
	/**
751
	 * Push callback which will be called only once, when writing is available next time
752
	 * @param  callable $cb Callback
753
	 * @return void
754
	 */
755
	public function onWriteOnce($cb) {
756
		if (!$this->writing) {
757
			call_user_func($cb, $this);
758
			return;
759
		}
760
		$this->onWriteOnce->push($cb);
761
	}
762
763
	/**
764
	 * Called when the connection is ready to accept new data
765
	 * @param  object $bev EventBufferEvent
766
	 * @return void
767
	 */
768
	public function onWriteEv($bev) {
0 ignored issues
show
Unused Code introduced by
The parameter $bev 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...
769
		if (Daemon::$config->logevents->value) {
770
			Daemon::log(get_class() . ' onWriteEv called');
771
		}
772
		$this->writing = false;
773
		if ($this->finished) {
774
			if ($this->bev->output->length === 0) {
775
				$this->close();
776
			}
777
			return;
778
		}
779
		if (!$this->ready) {
780
			$this->ready = true;
781
			while (!$this->onWriteOnce->isEmpty()) {
782
				try {
783
					$this->onWriteOnce->executeOne($this);
784
				} catch (\Exception $e) {
785
					Daemon::uncaughtExceptionHandler($e);
786
				}
787
				if (!$this->ready) {
788
					return;
789
				}
790
			}
791
			$this->alive = true;
792
			try {
793
				$this->onReady();
794
				if ($this->wRead) {
795
					$this->wRead = false;
796
					$this->onRead();
797
				}
798
			} catch (\Exception $e) {
799
				Daemon::uncaughtExceptionHandler($e);
800
			}
801
		}
802
		else {
803
			$this->onWriteOnce->executeAll($this);
804
		}
805
		try {
806
			$this->onWrite();
807
		} catch (\Exception $e) {
808
			Daemon::uncaughtExceptionHandler($e);
809
		}
810
	}
811
812
	/**
813
	 * Called when the connection state changed
814
	 * @param  object  $bev    EventBufferEvent
815
	 * @param  integer $events Events
816
	 * @return void
817
	 */
818
	public function onStateEv($bev, $events) {
819
		if ($events & \EventBufferEvent::CONNECTED) {
820
			$this->onWriteEv($bev);
821
		}
822
		elseif ($events & (\EventBufferEvent::ERROR | \EventBufferEvent::EOF | \EventBufferEvent::TIMEOUT)) {
823
			try {
824
				if ($this->finished) {
825
					return;
826
				}
827
				if ($events & \EventBufferEvent::ERROR) {
828
					$errno = \EventUtil::getLastSocketErrno();
829
					if ($errno !== 0) {
830
						$this->log('Socket error #' . $errno . ':' . \EventUtil::getLastSocketError());
831
					}
832
					if ($this->ssl && $this->bev) {
833
						while ($err = $this->bev->sslError()) {
834
							$this->log('EventBufferEvent SSL error: ' . $err);
835
						}
836
					}
837
				}
838
				$this->finished = true;
839
				$this->onFinish();
840
				$this->close();
841
			} catch (\Exception $e) {
842
				Daemon::uncaughtExceptionHandler($e);
843
			}
844
		}
845
	}
846
847
	/**
848
	 * Moves arbitrary number of bytes from input buffer to given buffer
849
	 * @param  \EventBuffer $dest Destination nuffer
850
	 * @param  integer      $n    Max. number of bytes to move
851
	 * @return integer|false
852
	 */
853
	public function moveToBuffer(\EventBuffer $dest, $n) {
854
		if (!isset($this->bev)) {
855
			return false;
856
		}
857
		return $dest->appendFrom($this->bev->input, $n);
858
	}
859
860
	/**
861
	 * Moves arbitrary number of bytes from given buffer to output buffer
862
	 * @param  \EventBuffer $src Source buffer
863
	 * @param  integer      $n   Max. number of bytes to move
864
	 * @return integer|false
865
	 */
866
	public function writeFromBuffer(\EventBuffer $src, $n) {
867
		if (!isset($this->bev)) {
868
			return false;
869
		}
870
		$this->writing = true;
871
		return $this->bev->output->appendFrom($src, $n);
872
	}
873
874
	/**
875
	 * Read data from the connection's buffer
876
	 * @param  integer      $n Max. number of bytes to read
877
	 * @return string|false    Readed data
878
	 */
879
	public function read($n) {
880
		if ($n <= 0) {
881
			return '';
882
		}
883
		if (!isset($this->bev)) {
884
			return false;
885
		}
886
		$read = $this->bev->read($n);
887
		if ($read === null) {
888
			return false;
889
		}
890
		return $read;
891
	}
892
893
	/**
894
	 * Reads all data from the connection's buffer
895
	 * @return string Readed data
896
	 */
897 View Code Duplication
	public function readUnlimited() {
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...
898
		if (!isset($this->bev)) {
899
			return false;
900
		}
901
		$read = $this->bev->read($this->bev->input->length);
902
		if ($read === null) {
903
			return false;
904
		}
905
		return $read;
906
	}
907
}
908