Completed
Push — master ( fee4b3...141056 )
by Vasily
15:11
created

Connection::next()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 3
rs 10
c 2
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     Vasily Zorin <[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
	 * Converts array into hash
232
	 * @param array $array
233
	 * @return array $hash
234
	 */
235
	public function arrayToHash($array) {
236
		$hash = [];
237 View Code Duplication
		for ($i = 0, $s = sizeof($array) - 1; $i < $s; ++$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...
238
			$hash[$array[$i]] = $array[++$i];
239
		}
240
		return $hash;
241
	}
242
243
	/**
244
	 * @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...
245
	 * @param  string  $key
246
	 * @param  integer $timeout
247
	 * @return Lock
248
	 */
249
	public function lock($key, $timeout) {
250
		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...
251
	}
252
253
	/**
254
	 * Easy wrapper for queue of eval's
255
	 * @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...
256
	 * @return MultiEval
257
	 */
258
	public function meval($cb = null) {
259
		return new MultiEval($cb, $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...
Bug introduced by
It seems like $cb defined by parameter $cb on line 258 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...
260
	}
261
262
	/**
263
	 * Wrapper for scans commands
264
	 * @param  string  $cmd    Command
265
	 * @param  array   $args   Arguments
266
	 * @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...
267
	 * @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...
268
	 * @return AutoScan
269
	 */
270
	public function autoscan($cmd, $args = [], $cbEnd = null, $limit = null) {
271
		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...
272
	}
273
274
	/**
275
	 * @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...
276
	 * @param  string $chan
277
	 * @return integer
278
	 */
279
	public function getLocalSubscribersCount($chan) {
280
		if (!isset($this->subscribeCb[$chan])) {
281
			return 0;
282
		}
283
		return sizeof($this->subscribeCb[$chan]);
284
	}
285
286
	/**
287
	 * Called when the connection is handshaked (at low-level), and peer is ready to recv. data
288
	 * @return void
289
	 */
290
	public function onReady() {
291
		$this->ptr =& $this->result;
292
		if (!isset($this->password)) {
293
			if (isset($this->pool->config->select->value)) {
294
				$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...
295
			}
296
			parent::onReady();
297
			$this->setWatermark(null, $this->pool->maxAllowedPacket + 2);
298
			return;
299
		}
300
		$this->sendCommand('AUTH', [$this->password], function () {
301
			if ($this->result !== 'OK') {
302
				$this->log('Auth. error: ' . json_encode($this->result));
303
				$this->finish();
304
			}
305
			if (isset($this->pool->config->select->value)) {
306
				$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...
307
			}
308
			parent::onReady();
309
			$this->setWatermark(null, $this->pool->maxAllowedPacket + 2);
310
		});
311
	}
312
313
	/**
314
	 * Magic __call
315
	 * Example:
316
	 * $redis->lpush('mylist', microtime(true));
317
	 * @param  sting $cmd
318
	 * @param  array $args
319
	 * @return void
320
	 */
321
	public function __call($cmd, $args) {
322
		$cb = null;
323 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...
324
			$a = $args[$i];
325
			if ((is_array($a) || is_object($a)) && is_callable($a)) {
326
				$cb = CallbackWrapper::wrap($a);
327
				$args = array_slice($args, 0, $i);
328
				break;
329
			}
330
			elseif ($a !== null) {
331
				break;
332
			}
333
		}
334
		$cmd = strtoupper($cmd);
335
		$this->command($cmd, $args, $cb);
336
	}
337
338
	/**
339
	 * @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...
340
	 * @param  string   $name
341
	 * @param  array    $args
342
	 * @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...
343
	 * @callback $cb ( )
344
	 * @return void
345
	 */
346
	public function command($name, $args, $cb = null) {
347
		if ($name === 'MULTI') {
348
			$this->acquire();
349
		}
350
		// PUB/SUB handling
351
		elseif (substr($name, -9) === 'SUBSCRIBE') {
352
			if (!$this->subscribed) {
353
				$this->subscribed = true;
354
				$this->pool->servConnSub[$this->url] = $this;
355
				$this->acquire();
356
				$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...
357
			}
358
359
			$opcb = null;
360 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...
361
				$a = $args[$i];
362
				if ((is_array($a) || is_object($a)) && is_callable($a)) {
363
					$opcb = $cb;
364
					$cb = CallbackWrapper::wrap($a);
365
					$args = array_slice($args, 0, $i);
366
					break;
367
				}
368
				elseif ($a !== null) {
369
					break;
370
				}
371
			}
372
		}
373
		
374
		if ($name === 'SUBSCRIBE') {
375
			$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...
376
			$channels = [];
377 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...
378
				if (!is_array($arg)) {
379
					$arg = [$arg];
380
				}
381
				foreach ($arg as $chan) {
382
					$b = !isset($this->subscribeCb[$chan]);
383
					CallbackWrapper::addToArray($this->subscribeCb[$chan], $cb);
384
					if ($b) {
385
						$channels[] = $chan;
386
					} else {
387
						if ($opcb !== null) {
388
							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...
389
						}
390
					}
391
				}
392
			}
393
			if (sizeof($channels)) {
394
				$this->sendCommand($name, $channels, $opcb);
395
			}
396
		}
397
		elseif ($name === 'PSUBSCRIBE') {
398
			$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...
399
			$channels = [];
400 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...
401
				if (!is_array($arg)) {
402
					$arg = [$arg];
403
				}
404
				foreach ($arg as $chan) {
405
					$b = !isset($this->psubscribeCb[$chan]);
406
					CallbackWrapper::addToArray($this->psubscribeCb[$chan], $cb);
407
					if ($b) {
408
						$channels[] = $chan;
409
					} else {
410
						if ($opcb !== null) {
411
							call_user_func($opcb, $this);
412
						}
413
					}
414
				}
415
			}
416
			if (sizeof($channels)) {
417
				$this->sendCommand($name, $channels, $opcb);
418
			}
419
		}
420
		elseif ($name === 'UNSUBSCRIBE') {
421
			$channels = [];
422
			foreach ($args as $arg) {
423
				if (!is_array($arg)) {
424
					$arg = [$arg];
425
				}
426
				foreach ($arg as $chan) {
427
					if (!isset($this->subscribeCb[$chan])) {
428
						if ($opcb !== null) {
429
							call_user_func($opcb, $this);
430
						}
431
						return;
432
					}
433
					CallbackWrapper::removeFromArray($this->subscribeCb[$chan], $cb);
434 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...
435
						$channels[] = $chan;
436
						unset($this->subscribeCb[$chan]);
437
					} else {
438
						if ($opcb !== null) {
439
							call_user_func($opcb, $this);
440
						}
441
					}
442
				}
443
			}
444
			if (sizeof($channels)) {
445
				$this->sendCommand($name, $channels, $opcb);
446
			}
447
		}
448 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...
449
			
450
			/* Race-condition-free UNSUBSCRIBE */
451
452
			$old = $this->subscribeCb;
453
			$this->sendCommand('UNSUBSCRIBE', $args, function($redis) use ($cb, $args, $old) {
454
				if (!$redis) {
455
					call_user_func($cb, $redis);
456
					return;
457
				}
458
				foreach ($args as $arg) {
459
					if (!isset($this->subscribeCb[$arg])) {
460
						continue;
461
					}
462
					foreach ($old[$arg] as $oldcb) {
463
						CallbackWrapper::removeFromArray($this->subscribeCb[$arg], $oldcb);
464
					}
465
					if (!sizeof($this->subscribeCb[$arg])) {
466
						unset($this->subscribeCb[$arg]);
467
					}
468
				}
469
				if ($cb !== null) {
470
					call_user_func($cb, $this);
471
				}
472
			});
473
		}
474
		elseif ($name === 'PUNSUBSCRIBE') {
475
			$channels = [];
476
			foreach ($args as $arg) {
477
				if (!is_array($arg)) {
478
					$arg = [$arg];
479
				}
480
				foreach ($arg as $chan) {
481
					CallbackWrapper::removeFromArray($this->psubscribeCb[$chan], $cb);
482 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...
483
						$channels[] = $chan;
484
						unset($this->psubscribeCb[$chan]);
485
					} else {
486
						if ($opcb !== null) {
487
							call_user_func($opcb, $this);
488
						}
489
					}
490
				}
491
			}
492
			if (sizeof($channels)) {
493
				$this->sendCommand($name, $channels, $opcb);
494
			}
495
		}
496 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...
497
			
498
			/* Race-condition-free PUNSUBSCRIBE */
499
500
			$old = $this->psubscribeCb;
501
			$this->sendCommand('PUNSUBSCRIBE', $args, function($redis) use ($cb, $args, $old) {
502
				if (!$redis) {
503
					call_user_func($cb, $redis);
504
					return;
505
				}
506
				foreach ($args as $arg) {
507
					if (!isset($this->psubscribeCb[$arg])) {
508
						continue;
509
					}
510
					foreach ($old[$arg] as $oldcb) {
511
						CallbackWrapper::removeFromArray($this->psubscribeCb[$arg], $oldcb);
512
					}
513
					if (!sizeof($this->psubscribeCb[$arg])) {
514
						unset($this->psubscribeCb[$arg]);
515
					}
516
				}
517
				if ($cb !== null) {
518
					call_user_func($cb, $this);
519
				}
520
			});
521
		} else {
522
			if ($name === 'MGET') {
523
				$this->resultTypeStack->push(static::RESULT_TYPE_ARGSVALS);
524
				$this->argsStack->push($args);
525
			}
526
			elseif ($name === 'HMGET') {
527
				$this->resultTypeStack->push(static::RESULT_TYPE_ARGSVALS);
528
				$a = $args;
529
				array_shift($a);
530
				$this->argsStack->push($a);
531
			}
532
			elseif ($name === 'HMSET') {
533
				if (sizeof($args) === 2) {
534
					if (is_array($args[1])) {
535
						$newArgs = [$args[0]];
536
						foreach ($args[1] as $key => $value) {
537
							$newArgs[] = $key;
538
							$newArgs[] = $value;
539
						}
540
						$args = $newArgs;
541
					}
542
				}
543
			}
544
			elseif ($name === 'HGETALL') {
545
				$this->resultTypeStack->push(static::RESULT_TYPE_ASSOC);
546
			}
547
			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...
548
				$this->resultTypeStack->push(static::RESULT_TYPE_ASSOC);
549
			}
550
			else {
551
				$this->resultTypeStack->push(static::RESULT_TYPE_DEFAULT);
552
			}
553
554
			$this->sendCommand($name, $args, $cb);
555
556
			if ($name === 'EXEC' || $name === 'DISCARD') {
557
				$this->release();
558
			}
559
		}
560
	}
561
562
	/**
563
	 * @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...
564
	 * @param  string   $name
565
	 * @param  array    $args
566
	 * @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...
567
	 * @callback $cb ( )
568
	 * @return void
569
	 */
570
	public function sendCommand($name, $args, $cb = null) {
571
		if ($name === 'MULTI') {
572
			$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...
573
		} else {
574
			$this->onResponse($cb);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by parameter $cb on line 570 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...
575
		}
576
		if (!is_array($args)) {
577
			$args = [$args];
578
		}
579
		array_unshift($args, $name);
580
		$this->writeln('*' . sizeof($args));
581
		foreach ($args as $arg) {
582
			$this->writeln('$' . strlen($arg) . $this->EOL . $arg);
583
		}
584
		if ($name === 'MULTI') {
585
			if ($cb !== null) {
586
				call_user_func($cb, $this);
587
			}
588
		}
589
	}
590
591
	/**
592
	 * Called when connection finishes
593
	 * @return void
594
	 */
595
	public function onFinish() {
596
		parent::onFinish();
597
		if ($this->subscribed) {
598
			unset($this->pool->servConnSub[$this->url]);
599
		}
600
		/* we should reassign subscriptions */
601
		foreach ($this->subscribeCb as $sub => $cbs) {
602
			foreach ($cbs as $cb) {
603
				call_user_func([$this->pool, 'subscribe'], $sub, $cb);
604
			}
605
		}
606
		foreach ($this->psubscribeCb as $sub => $cbs) {
607
			foreach ($cbs as $cb) {
608
				call_user_func([$this->pool, 'psubscribe'], $sub, $cb);
609
			}
610
		}
611
	}
612
613
	protected function onPacket() {
614
		$this->result = $this->ptr;
615
		if (!$this->subscribed) {
616
			$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...
617 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...
618
				$this->args = !$this->argsStack->isEmpty() ? $this->argsStack->shift() : [];
619
			}
620
			$this->onResponse->executeOne($this);
621
			goto clean;
622
		} elseif ($this->result[0] === 'message') {
623
			$t = &$this->subscribeCb;
624
		} elseif ($this->result[0] === 'pmessage') {
625
			$t = &$this->psubscribeCb;
626
		} else {
627
			$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...
628 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...
629
				$this->args = !$this->argsStack->isEmpty() ? $this->argsStack->shift() : [];
630
			}
631
			$this->onResponse->executeOne($this);
632
			goto clean;
633
		}
634
		if (isset($t[$this->result[1]])) {
635
			$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...
636
			$this->channel = $this->result[1];
637
			$this->msg     = $this->result[2];
638
			foreach ($t[$this->result[1]] as $cb) {
639
				if (is_callable($cb)) {
640
					call_user_func($cb, $this);
641
				}
642
			}
643
		} elseif ($this->pool->config->logpubsubracecondition->value) {
644
			Daemon::log('[Redis client]'. ': PUB/SUB race condition at channel '. Debug::json($this->result[1]));
645
		}
646
		clean:
647
		$this->args       = null;
648
		$this->result     = null;
649
		$this->channel    = null;
650
		$this->msg        = null;
651
		$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...
652
		$this->pos        = 0;
653
		$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...
654
		$this->assocData  = null;
655
		if (!isset($t)) {
656
			$this->checkFree();
657
		}
658
	}
659
660
	/**
661
	 * @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...
662
	 * @param  mixed $val
663
	 * @return void
664
	 */
665
	public function pushValue($val) {
666
		if (is_array($this->ptr)) {
667
			$this->ptr[] = $val;
668
		} else {
669
			$this->ptr = $val;
670
		}
671
		start:
672
		if (sizeof($this->ptr) < $this->levelLength) {
673
			return;
674
		}
675
		array_pop($this->stack);
676
		if (!sizeof($this->stack)) {
677
			$this->levelLength = null;
678
			$this->onPacket();
679
			$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...
680
			$this->ptr = null;
681
			return;
682
		}
683
684
		$this->ptr =& $dummy;
685
686
		list ($this->ptr, $this->levelLength) = end($this->stack);
687
688
		goto start;
689
	}
690
691
	/**
692
	 * Called when new data received
693
	 * @return void
694
	 */
695
	protected function onRead() {
696
		start:
697
		if ($this->state === static::STATE_STANDBY) { // outside of packet
698
			while (($l = $this->readline()) !== null) {
699
				if ($l === '') {
700
					continue;
701
				}
702
				$char = $l{0};
703
				if ($char === ':') { // inline integer
704
					$this->pushValue((int) substr($l, 1));
705
					goto start;
706
				}
707
				elseif (($char === '+') || ($char === '-')) { // inline string
708
					$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...
709
					$this->pushValue(substr($l, 1));
710
					goto start;
711
				}
712
				elseif ($char === '*') { // defines number of elements of incoming array
713
					$length = (int) substr($l, 1);
714
					if ($length <= 0) {
715
						$this->pushValue([]);
716
						goto start;
717
					}
718
719
					$ptr = [];
720
					
721
					if (is_array($this->ptr)) {
722
						$this->ptr[] =& $ptr;
723
					}
724
725
					$this->ptr =& $ptr;
726
					$this->stack[] = [&$ptr, $length];
727
					$this->levelLength = $length;
728
					unset($ptr);
729
730
					goto start;
731
				}
732
				elseif ($char === '$') { // defines size of the data block
733
					if ($l{1} === '-') {
734
						$this->pushValue(null);
735
						goto start;
736
					}
737
					$this->valueLength = (int)substr($l, 1);
738
					if ($this->valueLength + 2 > $this->pool->maxAllowedPacket) {
739
						$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...
740
						$this->finish();
741
						return;
742
					}
743
					$this->setWatermark($this->valueLength + 2);
744
					$this->state = static::STATE_BINARY; // binary data block
745
					break; // stop reading line-by-line
746
				}
747
			}
748
		}
749
750
		if ($this->state === static::STATE_BINARY) { // inside of binary data block
751
			if ($this->getInputLength() < $this->valueLength + 2) {
752
				return; //we do not have a whole packet
753
			}
754
			$value = $this->read($this->valueLength);
755
			if ($this->read(2) !== $this->EOL) {
756
				$this->finish();
757
				return;
758
			}
759
			$this->state = static::STATE_STANDBY;
760
			$this->setWatermark(3);
761
			$this->pushValue($value);
762
			goto start;
763
		}
764
	}
765
}
766