Completed
Push — master ( cc6179...0f8db1 )
by Vasily
04:46
created

Connection::rewind()   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 0
1
<?php
2
namespace PHPDaemon\Clients\Redis;
3
4
use PHPDaemon\Network\ClientConnection;
5
use PHPDaemon\Core\Daemon;
6
use PHPDaemon\Core\Debug;
7
use PHPDaemon\Core\CallbackWrapper;
8
9
/**
10
 * @package    NetworkClients
11
 * @subpackage RedisClient
12
 * @author     Zorin Vasily <[email protected]>
13
 */
14
class Connection extends ClientConnection implements \Iterator {
15
	/**
16
	 * Default flag
17
	 */
18
	const RESULT_TYPE_DEFAULT = 0;
19
20
	/**
21
	 * Flag - parse message response
22
	 */
23
	const RESULT_TYPE_MESSAGE = 1;
24
25
	/**
26
	 * Flag - parse response with arguments
27
	 */
28
	const RESULT_TYPE_ARGSVALS = 2;
29
30
	/**
31
	 * Flag - parse response to associative array
32
	 */
33
	const RESULT_TYPE_ASSOC = 3;
34
35
	/**
36
	 * @var array|null Current result
37
	 */
38
	public $result = null;
39
40
	/**
41
	 * @var string Channel name
42
	 */
43
	public $channel = null;
44
45
	/**
46
	 * @var string Message
47
	 */
48
	public $msg = null;
49
50
	/**
51
	 * @var string Current error message
52
	 */
53
	public $error;
54
55
	/**
56
	 * @var array Subcriptions
57
	 */
58
	public $subscribeCb = [];
59
60
	public $psubscribeCb = [];
61
62
	/**
63
	 * @var string Current incoming key
64
	 */
65
	protected $key;
66
67
	protected $stack = [];
68
	
69
	protected $ptr;
70
71
	/**
72
	 * @var integer Current value length
73
	 */
74
	protected $valueLength = 0;
75
76
	/**
77
	 * @var integer Current level length
78
	 */
79
	protected $levelLength = null;
80
81
	/**
82
	 * @var string
83
	 */
84
	protected $EOL = "\r\n";
85
86
	/**
87
	 * @var boolean Is it a subscription connection?
88
	 */
89
	protected $subscribed = false;
90
91
	/**
92
	 * @var float Timeout
93
	 */
94
	protected $timeoutRead = 5;
95
96
	/**
97
	 * @var integer Iterator position
98
	 */
99
	protected $pos = 0;
100
101
	/**
102
	 * @var \SplStack Stack of results types 
103
	 */
104
	protected $resultTypeStack;
105
106
	/**
107
	 * @var null|array Current result type
108
	 */
109
	protected $resultType = 0;
110
111
	/**
112
	 * @var \SplStack Stack of commands arguments
113
	 */
114
	protected $argsStack;
115
116
	/**
117
	 * @var null|array Current arguments
118
	 */
119
	protected $args = null;
120
121
	/**
122
	 * @var null|array Associative result storage
123
	 */
124
	protected $assocData = null;
125
126
	/**
127
	 * In the middle of binary response part
128
	 */
129
	const STATE_BINARY = 1;
130
131
	public function __construct($fd, $pool = null) {
132
		parent::__construct($fd, $pool);
133
		$this->resultTypeStack = new \SplStack;
134
		$this->argsStack       = new \SplStack;
135
	}
136
137
	public function rewind() {
138
		$this->pos = 0;
139
	}
140
141 View Code Duplication
	public function current() {
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...
142
		if (!is_array($this->result)) {
143
			return $this->pos === 0 ? $this->result : null;
144
		}
145
		elseif ($this->resultType === static::RESULT_TYPE_DEFAULT || $this->resultType === static::RESULT_TYPE_ARGSVALS) {
146
			return $this->result[$this->pos];
147
		}
148
		elseif ($this->resultType === static::RESULT_TYPE_MESSAGE) {
149
			// message
150
			return $this->result[2];
151
		}
152
		elseif ($this->resultType === static::RESULT_TYPE_ASSOC) {
153
			return $this->result[$this->pos * 2 + 1];
154
		}
155
	}
156
157 View Code Duplication
	public function key() {
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...
158
		if (!is_array($this->result)) {
159
			return $this->pos === 0 ? 0 : null;
160
		}
161
		elseif ($this->resultType === static::RESULT_TYPE_DEFAULT) {
162
			return $this->pos;
163
		}
164
		elseif ($this->resultType === static::RESULT_TYPE_MESSAGE) {
165
			// channel
166
			return $this->result[1];
167
		}
168
		elseif ($this->resultType === static::RESULT_TYPE_ARGSVALS) {
169
			return $this->args[$this->pos];
170
		}
171
		elseif ($this->resultType === static::RESULT_TYPE_ASSOC) {
172
			return $this->result[$this->pos * 2];
173
		}
174
	}
175
176
	public function next() {
177
		++$this->pos;
178
	}
179
180
	public function valid() {
181
		if (!is_array($this->result)) {
182
			return false;
183
		}
184
		elseif ($this->resultType === static::RESULT_TYPE_DEFAULT) {
185
			return isset($this->result[$this->pos]);
186
		}
187
		elseif ($this->resultType === static::RESULT_TYPE_ARGSVALS) {
188
			return isset($this->args[$this->pos]) && isset($this->result[$this->pos]);
189
		}
190
		elseif ($this->resultType === static::RESULT_TYPE_MESSAGE || $this->resultType === static::RESULT_TYPE_ASSOC) {
191
			return isset($this->result[$this->pos * 2 + 1]);
192
		}
193
	}
194
195
	/**
196
	 * Get associative result
197
	 * @param  string $name
198
	 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|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...
199
	 */
200
	public function __get($name) {
201
		if ($name === 'assoc') {
202
			if ($this->assocData === null) {
203
				if(!is_array($this->result) || empty($this->result)) {
204
					$this->assocData = [];
205
				}
206
				elseif ($this->resultType === static::RESULT_TYPE_MESSAGE) {
207
					$this->assocData = [ $this->result[1] => $this->result[2] ];
208
				}
209 View Code Duplication
				elseif ($this->resultType === static::RESULT_TYPE_ARGSVALS) {
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...
210
					$hash = [];
211
					for ($i = 0, $s = sizeof($this->result); $i < $s; ++$i) {
212
						$hash[$this->args[$i]] = $this->result[$i];
213
					}
214
					$this->assocData = $hash;
215
				}
216 View Code Duplication
				elseif ($this->resultType === static::RESULT_TYPE_ASSOC) {
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
					$hash = [];
218
					for ($i = 0, $s = sizeof($this->result) - 1; $i < $s; ++$i) {
219
						$hash[$this->result[$i]] = $this->result[++$i];
220
					}
221
					$this->assocData = $hash;
222
				} else {
223
					$this->assocData = $this->result;
224
				}
225
			}
226
			return $this->assocData;
227
		}
228
	}
229
230
	/**
231
	 * @TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
232
	 * @param  string  $key
233
	 * @param  integer $timeout
234
	 * @return Lock
235
	 */
236
	public function lock($key, $timeout) {
237
		return new Lock($key, $timeout, $this);
0 ignored issues
show
Documentation introduced by
$this is of type this<PHPDaemon\Clients\Redis\Connection>, but the function expects a object<PHPDaemon\Clients\Redis\Pool>.

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...
238
	}
239
240
	/**
241
	 * Easy wrapper for queue of eval's
242
	 * @param  callable  $cb
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...
243
	 * @return MultiEval
244
	 */
245
	public function meval($cb = null) {
246
		return new MultiEval($cb, $this);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by parameter $cb on line 245 can also be of type null; however, PHPDaemon\Clients\Redis\MultiEval::__construct() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Documentation introduced by
$this is of type this<PHPDaemon\Clients\Redis\Connection>, but the function expects a object<PHPDaemon\Clients\Redis\Pool>.

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...
247
	}
248
249
	/**
250
	 * Wrapper for scans commands
251
	 * @param  string  $cmd    Command
252
	 * @param  array   $args   Arguments
253
	 * @param  cllable $cbEnd  Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cbEnd not be cllable|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...
254
	 * @param  integer $limit  Limit
0 ignored issues
show
Documentation introduced by
Should the type for parameter $limit 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...
255
	 * @return AutoScan
256
	 */
257
	public function autoscan($cmd, $args = [], $cbEnd = null, $limit = null) {
258
		return new AutoScan($this, $cmd, $args, $cbEnd, $limit);
0 ignored issues
show
Documentation introduced by
$this is of type this<PHPDaemon\Clients\Redis\Connection>, but the function expects a object<PHPDaemon\Clients\Redis\Pool>.

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...
259
	}
260
261
	/**
262
	 * @TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
263
	 * @param  string $chan
264
	 * @return integer
265
	 */
266
	public function getLocalSubscribersCount($chan) {
267
		if (!isset($this->subscribeCb[$chan])) {
268
			return 0;
269
		}
270
		return sizeof($this->subscribeCb[$chan]);
271
	}
272
273
	/**
274
	 * Called when the connection is handshaked (at low-level), and peer is ready to recv. data
275
	 * @return void
276
	 */
277
	public function onReady() {
278
		$this->ptr =& $this->result;
279
		if (!isset($this->password)) {
280
			if (isset($this->pool->config->select->value)) {
281
				$this->select($this->pool->config->select->value);
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<PHPDaemon\Clients\Redis\Connection>? 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...
282
			}
283
			parent::onReady();
284
			$this->setWatermark(null, $this->pool->maxAllowedPacket + 2);
285
			return;
286
		}
287
		$this->sendCommand('AUTH', [$this->password], function () {
288
			if ($this->result !== 'OK') {
289
				$this->log('Auth. error: ' . json_encode($this->result));
290
				$this->finish();
291
			}
292
			if (isset($this->pool->config->select->value)) {
293
				$this->select($this->pool->config->select->value);
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<PHPDaemon\Clients\Redis\Connection>? 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...
294
			}
295
			parent::onReady();
296
			$this->setWatermark(null, $this->pool->maxAllowedPacket + 2);
297
		});
298
	}
299
300
	/**
301
	 * Magic __call
302
	 * Example:
303
	 * $redis->lpush('mylist', microtime(true));
304
	 * @param  sting $cmd
305
	 * @param  array $args
306
	 * @return void
307
	 */
308
	public function __call($cmd, $args) {
309
		$cb = null;
310 View Code Duplication
		for ($i = sizeof($args) - 1; $i >= 0; --$i) {
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
			$a = $args[$i];
312
			if ((is_array($a) || is_object($a)) && is_callable($a)) {
313
				$cb = CallbackWrapper::wrap($a);
314
				$args = array_slice($args, 0, $i);
315
				break;
316
			}
317
			elseif ($a !== null) {
318
				break;
319
			}
320
		}
321
		$cmd = strtoupper($cmd);
322
		$this->command($cmd, $args, $cb);
323
	}
324
325
	/**
326
	 * @TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
327
	 * @param  string   $name
328
	 * @param  array    $args
329
	 * @param  callable $cb
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...
330
	 * @callback $cb ( )
331
	 * @return void
332
	 */
333
	public function command($name, $args, $cb = null) {
334
		if ($name === 'MULTI') {
335
			$this->acquire();
336
		}
337
		// PUB/SUB handling
338
		elseif (substr($name, -9) === 'SUBSCRIBE') {
339
			if (!$this->subscribed) {
340
				$this->subscribed = true;
341
				$this->pool->servConnSub[$this->url] = $this;
342
				$this->acquire();
343
				$this->setTimeouts(86400, 86400); // @TODO: remove timeout
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
344
			}
345
346
			$opcb = null;
347 View Code Duplication
			for ($i = sizeof($args) - 1; $i >= 0; --$i) {
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...
348
				$a = $args[$i];
349
				if ((is_array($a) || is_object($a)) && is_callable($a)) {
350
					$opcb = $cb;
351
					$cb = CallbackWrapper::wrap($a);
352
					$args = array_slice($args, 0, $i);
353
					break;
354
				}
355
				elseif ($a !== null) {
356
					break;
357
				}
358
			}
359
		}
360
		
361
		if ($name === 'SUBSCRIBE') {
362
			$this->subscribed();
0 ignored issues
show
Documentation Bug introduced by
The method subscribed does not exist on object<PHPDaemon\Clients\Redis\Connection>? 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...
363
			$channels = [];
364 View Code Duplication
			foreach ($args as $arg) {
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...
365
				if (!is_array($arg)) {
366
					$arg = [$arg];
367
				}
368
				foreach ($arg as $chan) {
369
					$b = !isset($this->subscribeCb[$chan]);
370
					CallbackWrapper::addToArray($this->subscribeCb[$chan], $cb);
371
					if ($b) {
372
						$channels[] = $chan;
373
					} else {
374
						if ($opcb !== null) {
375
							call_user_func($opcb, $this);
0 ignored issues
show
Bug introduced by
The variable $opcb does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
376
						}
377
					}
378
				}
379
			}
380
			if (sizeof($channels)) {
381
				$this->sendCommand($name, $channels, $opcb);
382
			}
383
		}
384
		elseif ($name === 'PSUBSCRIBE') {
385
			$this->subscribed();
0 ignored issues
show
Documentation Bug introduced by
The method subscribed does not exist on object<PHPDaemon\Clients\Redis\Connection>? 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...
386
			$channels = [];
387 View Code Duplication
			foreach ($args as $arg) {
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...
388
				if (!is_array($arg)) {
389
					$arg = [$arg];
390
				}
391
				foreach ($arg as $chan) {
392
					$b = !isset($this->psubscribeCb[$chan]);
393
					CallbackWrapper::addToArray($this->psubscribeCb[$chan], $cb);
394
					if ($b) {
395
						$channels[] = $chan;
396
					} else {
397
						if ($opcb !== null) {
398
							call_user_func($opcb, $this);
399
						}
400
					}
401
				}
402
			}
403
			if (sizeof($channels)) {
404
				$this->sendCommand($name, $channels, $opcb);
405
			}
406
		}
407
		elseif ($name === 'UNSUBSCRIBE') {
408
			$channels = [];
409
			foreach ($args as $arg) {
410
				if (!is_array($arg)) {
411
					$arg = [$arg];
412
				}
413
				foreach ($arg as $chan) {
414
					if (!isset($this->subscribeCb[$chan])) {
415
						if ($opcb !== null) {
416
							call_user_func($opcb, $this);
417
						}
418
						return;
419
					}
420
					CallbackWrapper::removeFromArray($this->subscribeCb[$chan], $cb);
421 View Code Duplication
					if (sizeof($this->subscribeCb[$chan]) === 0) {
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...
422
						$channels[] = $chan;
423
						unset($this->subscribeCb[$chan]);
424
					} else {
425
						if ($opcb !== null) {
426
							call_user_func($opcb, $this);
427
						}
428
					}
429
				}
430
			}
431
			if (sizeof($channels)) {
432
				$this->sendCommand($name, $channels, $opcb);
433
			}
434
		}
435 View Code Duplication
		elseif ($name === 'UNSUBSCRIBEREAL') {
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...
436
			
437
			/* Race-condition-free UNSUBSCRIBE */
438
439
			$old = $this->subscribeCb;
440
			$this->sendCommand('UNSUBSCRIBE', $args, function($redis) use ($cb, $args, $old) {
441
				if (!$redis) {
442
					call_user_func($cb, $redis);
443
					return;
444
				}
445
				foreach ($args as $arg) {
446
					if (!isset($this->subscribeCb[$arg])) {
447
						continue;
448
					}
449
					foreach ($old[$arg] as $oldcb) {
450
						CallbackWrapper::removeFromArray($this->subscribeCb[$arg], $oldcb);
451
					}
452
					if (!sizeof($this->subscribeCb[$arg])) {
453
						unset($this->subscribeCb[$arg]);
454
					}
455
				}
456
				if ($cb !== null) {
457
					call_user_func($cb, $this);
458
				}
459
			});
460
		}
461
		elseif ($name === 'PUNSUBSCRIBE') {
462
			$channels = [];
463
			foreach ($args as $arg) {
464
				if (!is_array($arg)) {
465
					$arg = [$arg];
466
				}
467
				foreach ($arg as $chan) {
468
					CallbackWrapper::removeFromArray($this->psubscribeCb[$chan], $cb);
469 View Code Duplication
					if (sizeof($this->psubscribeCb[$chan]) === 0) {
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...
470
						$channels[] = $chan;
471
						unset($this->psubscribeCb[$chan]);
472
					} else {
473
						if ($opcb !== null) {
474
							call_user_func($opcb, $this);
475
						}
476
					}
477
				}
478
			}
479
			if (sizeof($channels)) {
480
				$this->sendCommand($name, $channels, $opcb);
481
			}
482
		}
483 View Code Duplication
		elseif ($name === 'PUNSUBSCRIBEREAL') {
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...
484
			
485
			/* Race-condition-free PUNSUBSCRIBE */
486
487
			$old = $this->psubscribeCb;
488
			$this->sendCommand('PUNSUBSCRIBE', $args, function($redis) use ($cb, $args, $old) {
489
				if (!$redis) {
490
					call_user_func($cb, $redis);
491
					return;
492
				}
493
				foreach ($args as $arg) {
494
					if (!isset($this->psubscribeCb[$arg])) {
495
						continue;
496
					}
497
					foreach ($old[$arg] as $oldcb) {
498
						CallbackWrapper::removeFromArray($this->psubscribeCb[$arg], $oldcb);
499
					}
500
					if (!sizeof($this->psubscribeCb[$arg])) {
501
						unset($this->psubscribeCb[$arg]);
502
					}
503
				}
504
				if ($cb !== null) {
505
					call_user_func($cb, $this);
506
				}
507
			});
508
		} else {
509
			if ($name === 'MGET') {
510
				$this->resultTypeStack->push(static::RESULT_TYPE_ARGSVALS);
511
				$this->argsStack->push($args);
512
			}
513
			elseif ($name === 'HMGET') {
514
				$this->resultTypeStack->push(static::RESULT_TYPE_ARGSVALS);
515
				$a = $args;
516
				array_shift($a);
517
				$this->argsStack->push($a);
518
			}
519
			elseif ($name === 'HGETALL') {
520
				$this->resultTypeStack->push(static::RESULT_TYPE_ASSOC);
521
			}
522
			elseif (($name === 'ZRANGE' || $name === 'ZRANGEBYSCORE' || $name === 'ZREVRANGE' || $name === 'ZREVRANGEBYSCORE') && preg_grep('/WITHSCORES/i', $args)){
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 156 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...
523
				$this->resultTypeStack->push(static::RESULT_TYPE_ASSOC);
524
			}
525
			else {
526
				$this->resultTypeStack->push(static::RESULT_TYPE_DEFAULT);
527
			}
528
529
			$this->sendCommand($name, $args, $cb);
530
531
			if ($name === 'EXEC' || $name === 'DISCARD') {
532
				$this->release();
533
			}
534
		}
535
	}
536
537
	/**
538
	 * @TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
539
	 * @param  string   $name
540
	 * @param  array    $args
541
	 * @param  callable $cb
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...
542
	 * @callback $cb ( )
543
	 * @return void
544
	 */
545
	public function sendCommand($name, $args, $cb = null) {
546
		if ($name === 'MULTI') {
547
			$this->onResponse(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a callable.

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...
548
		} else {
549
			$this->onResponse($cb);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by parameter $cb on line 545 can also be of type null; however, PHPDaemon\Network\ClientConnection::onResponse() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
550
		}
551
		if (!is_array($args)) {
552
			$args = [$args];
553
		}
554
		array_unshift($args, $name);
555
		$this->writeln('*' . sizeof($args));
556
		foreach ($args as $arg) {
557
			$this->writeln('$' . strlen($arg) . $this->EOL . $arg);
558
		}
559
		if ($name === 'MULTI') {
560
			call_user_func($cb, $this);
561
		}
562
	}
563
564
	/**
565
	 * Called when connection finishes
566
	 * @return void
567
	 */
568
	public function onFinish() {
569
		parent::onFinish();
570
		if ($this->subscribed) {
571
			unset($this->pool->servConnSub[$this->url]);
572
		}
573
		/* we should reassign subscriptions */
574
		foreach ($this->subscribeCb as $sub => $cbs) {
575
			foreach ($cbs as $cb) {
576
				call_user_func([$this->pool, 'subscribe'], $sub, $cb);
577
			}
578
		}
579
		foreach ($this->psubscribeCb as $sub => $cbs) {
580
			foreach ($cbs as $cb) {
581
				call_user_func([$this->pool, 'psubscribe'], $sub, $cb);
582
			}
583
		}
584
	}
585
586
	protected function onPacket() {
587
		$this->result = $this->ptr;
588
		if (!$this->subscribed) {
589
			$this->resultType = !$this->resultTypeStack->isEmpty() ? $this->resultTypeStack->shift() : static::RESULT_TYPE_DEFAULT;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 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...
590 View Code Duplication
			if ($this->resultType === static::RESULT_TYPE_ARGSVALS) {
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...
591
				$this->args = !$this->argsStack->isEmpty() ? $this->argsStack->shift() : [];
592
			}
593
			$this->onResponse->executeOne($this);
594
			goto clean;
595
		} elseif ($this->result[0] === 'message') {
596
			$t = &$this->subscribeCb;
597
		} elseif ($this->result[0] === 'pmessage') {
598
			$t = &$this->psubscribeCb;
599
		} else {
600
			$this->resultType = !$this->resultTypeStack->isEmpty() ? $this->resultTypeStack->shift() : static::RESULT_TYPE_DEFAULT;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 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...
601 View Code Duplication
			if ($this->resultType === static::RESULT_TYPE_ARGSVALS) {
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...
602
				$this->args = !$this->argsStack->isEmpty() ? $this->argsStack->shift() : [];
603
			}
604
			$this->onResponse->executeOne($this);
605
			goto clean;
606
		}
607
		if (isset($t[$this->result[1]])) {
608
			$this->resultType = static::RESULT_TYPE_MESSAGE;
0 ignored issues
show
Documentation Bug introduced by
It seems like static::RESULT_TYPE_MESSAGE of type integer is incompatible with the declared type null|array of property $resultType.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
609
			$this->channel = $this->result[1];
610
			$this->msg     = $this->result[2];
611
			foreach ($t[$this->result[1]] as $cb) {
612
				if (is_callable($cb)) {
613
					call_user_func($cb, $this);
614
				}
615
			}
616
		} elseif ($this->pool->config->logpubsubracecondition->value) {
617
			Daemon::log('[Redis client]'. ': PUB/SUB race condition at channel '. Debug::json($this->result[1]));
618
		}
619
		clean:
620
		$this->args       = null;
621
		$this->result     = null;
622
		$this->channel    = null;
623
		$this->msg        = null;
624
		$this->error      = false;
0 ignored issues
show
Documentation Bug introduced by
The property $error was declared of type string, but false is of type false. 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...
625
		$this->pos        = 0;
626
		$this->resultType = static::RESULT_TYPE_DEFAULT;
0 ignored issues
show
Documentation Bug introduced by
It seems like static::RESULT_TYPE_DEFAULT of type integer is incompatible with the declared type null|array of property $resultType.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
627
		$this->assocData  = null;
628
		if (!isset($t)) {
629
			$this->checkFree();
630
		}
631
	}
632
633
	/**
634
	 * @TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
635
	 * @param  mixed $val
636
	 * @return void
637
	 */
638
	public function pushValue($val) {
639
		if (is_array($this->ptr)) {
640
			$this->ptr[] = $val;
641
		} else {
642
			$this->ptr = $val;
643
		}
644
		start:
645
		if (sizeof($this->ptr) < $this->levelLength) {
646
			return;
647
		}
648
		array_pop($this->stack);
649
		if (!sizeof($this->stack)) {
650
			$this->levelLength = null;
651
			$this->onPacket();
652
			$this->ptr =& $dummy;
0 ignored issues
show
Bug introduced by
The variable $dummy does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
653
			$this->ptr = null;
654
			return;
655
		}
656
657
		$this->ptr =& $dummy;
658
659
		list ($this->ptr, $this->levelLength) = end($this->stack);
660
661
		goto start;
662
	}
663
664
	/**
665
	 * Called when new data received
666
	 * @return void
667
	 */
668
	protected function onRead() {
669
		start:
670
		if ($this->state === static::STATE_STANDBY) { // outside of packet
671
			while (($l = $this->readline()) !== null) {
672
				if ($l === '') {
673
					continue;
674
				}
675
				$char = $l{0};
676
				if ($char === ':') { // inline integer
677
					$this->pushValue((int) substr($l, 1));
678
					goto start;
679
				}
680
				elseif (($char === '+') || ($char === '-')) { // inline string
681
					$this->error = $char === '-';
0 ignored issues
show
Documentation Bug introduced by
The property $error was declared of type string, but $char === '-' is of type boolean. 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...
682
					$this->pushValue(substr($l, 1));
683
					goto start;
684
				}
685
				elseif ($char === '*') { // defines number of elements of incoming array
686
					$length = (int) substr($l, 1);
687
					if ($length <= 0) {
688
						$this->pushValue([]);
689
						goto start;
690
					}
691
692
					$ptr = [];
693
					
694
					if (is_array($this->ptr)) {
695
						$this->ptr[] =& $ptr;
696
					}
697
698
					$this->ptr =& $ptr;
699
					$this->stack[] = [&$ptr, $length];
700
					$this->levelLength = $length;
701
					unset($ptr);
702
703
					goto start;
704
				}
705
				elseif ($char === '$') { // defines size of the data block
706
					if ($l{1} === '-') {
707
						$this->pushValue(null);
708
						goto start;
709
					}
710
					$this->valueLength = (int)substr($l, 1);
711
					if ($this->valueLength + 2 > $this->pool->maxAllowedPacket) {
712
						$this->log('max-allowed-packet ('.$this->pool->config->maxallowedpacket->getHumanValue().') exceed, aborting connection');
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...
713
						$this->finish();
714
						return;
715
					}
716
					$this->setWatermark($this->valueLength + 2);
717
					$this->state = static::STATE_BINARY; // binary data block
718
					break; // stop reading line-by-line
719
				}
720
			}
721
		}
722
723
		if ($this->state === static::STATE_BINARY) { // inside of binary data block
724
			if ($this->getInputLength() < $this->valueLength + 2) {
725
				return; //we do not have a whole packet
726
			}
727
			$value = $this->read($this->valueLength);
728
			if ($this->read(2) !== $this->EOL) {
729
				$this->finish();
730
				return;
731
			}
732
			$this->state = static::STATE_STANDBY;
733
			$this->setWatermark(3);
734
			$this->pushValue($value);
735
			goto start;
736
		}
737
	}
738
}
739