Completed
Push — mater ( 02259a )
by Vasily
05:33
created

Connection::autoscan()   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
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 4
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
		$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...
547
		if (!is_array($args)) {
548
			$args = [$args];
549
		}
550
		array_unshift($args, $name);
551
		$this->writeln('*' . sizeof($args));
552
		foreach ($args as $arg) {
553
			$this->writeln('$' . strlen($arg) . $this->EOL . $arg);
554
		}
555
	}
556
557
	/**
558
	 * Called when connection finishes
559
	 * @return void
560
	 */
561
	public function onFinish() {
562
		parent::onFinish();
563
		if ($this->subscribed) {
564
			unset($this->pool->servConnSub[$this->url]);
565
		}
566
		/* we should reassign subscriptions */
567
		foreach ($this->subscribeCb as $sub => $cbs) {
568
			foreach ($cbs as $cb) {
569
				call_user_func([$this->pool, 'subscribe'], $sub, $cb);
570
			}
571
		}
572
		foreach ($this->psubscribeCb as $sub => $cbs) {
573
			foreach ($cbs as $cb) {
574
				call_user_func([$this->pool, 'psubscribe'], $sub, $cb);
575
			}
576
		}
577
	}
578
579
	protected function onPacket() {
580
		$this->result = $this->ptr;
581
		if (!$this->subscribed) {
582
			$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...
583 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...
584
				$this->args = !$this->argsStack->isEmpty() ? $this->argsStack->shift() : [];
585
			}
586
			$this->onResponse->executeOne($this);
587
			goto clean;
588
		} elseif ($this->result[0] === 'message') {
589
			$t = &$this->subscribeCb;
590
		} elseif ($this->result[0] === 'pmessage') {
591
			$t = &$this->psubscribeCb;
592
		} else {
593
			$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...
594 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...
595
				$this->args = !$this->argsStack->isEmpty() ? $this->argsStack->shift() : [];
596
			}
597
			$this->onResponse->executeOne($this);
598
			goto clean;
599
		}
600
		if (isset($t[$this->result[1]])) {
601
			$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...
602
			$this->channel = $this->result[1];
603
			$this->msg     = $this->result[2];
604
			foreach ($t[$this->result[1]] as $cb) {
605
				if (is_callable($cb)) {
606
					call_user_func($cb, $this);
607
				}
608
			}
609
		} elseif ($this->pool->config->logpubsubracecondition->value) {
610
			Daemon::log('[Redis client]'. ': PUB/SUB race condition at channel '. Debug::json($this->result[1]));
611
		}
612
		clean:
613
		$this->args       = null;
614
		$this->result     = null;
615
		$this->channel    = null;
616
		$this->msg        = null;
617
		$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...
618
		$this->pos        = 0;
619
		$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...
620
		$this->assocData  = null;
621
		if (!isset($t)) {
622
			$this->checkFree();
623
		}
624
	}
625
626
	/**
627
	 * @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...
628
	 * @param  mixed $val
629
	 * @return void
630
	 */
631
	public function pushValue($val) {
632
		if (is_array($this->ptr)) {
633
			$this->ptr[] = $val;
634
		} else {
635
			$this->ptr = $val;
636
		}
637
		start:
638
		if (sizeof($this->ptr) < $this->levelLength) {
639
			return;
640
		}
641
		array_pop($this->stack);
642
		if (!sizeof($this->stack)) {
643
			$this->levelLength = null;
644
			$this->onPacket();
645
			$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...
646
			$this->ptr = null;
647
			return;
648
		}
649
650
		$this->ptr =& $dummy;
651
652
		list ($this->ptr, $this->levelLength) = end($this->stack);
653
654
		goto start;
655
	}
656
657
	/**
658
	 * Called when new data received
659
	 * @return void
660
	 */
661
	protected function onRead() {
662
		start:
663
		if ($this->state === static::STATE_STANDBY) { // outside of packet
664
			while (($l = $this->readline()) !== null) {
665
				if ($l === '') {
666
					continue;
667
				}
668
				$char = $l{0};
669
				if ($char === ':') { // inline integer
670
					$this->pushValue((int) substr($l, 1));
671
					goto start;
672
				}
673
				elseif (($char === '+') || ($char === '-')) { // inline string
674
					$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...
675
					$this->pushValue(substr($l, 1));
676
					goto start;
677
				}
678
				elseif ($char === '*') { // defines number of elements of incoming array
679
					$length = (int) substr($l, 1);
680
					if ($length <= 0) {
681
						$this->pushValue([]);
682
						goto start;
683
					}
684
685
					$ptr = [];
686
					
687
					if (is_array($this->ptr)) {
688
						$this->ptr[] =& $ptr;
689
					}
690
691
					$this->ptr =& $ptr;
692
					$this->stack[] = [&$ptr, $length];
693
					$this->levelLength = $length;
694
					unset($ptr);
695
696
					goto start;
697
				}
698
				elseif ($char === '$') { // defines size of the data block
699
					if ($l{1} === '-') {
700
						$this->pushValue(null);
701
						goto start;
702
					}
703
					$this->valueLength = (int)substr($l, 1);
704
					if ($this->valueLength + 2 > $this->pool->maxAllowedPacket) {
705
						$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...
706
						$this->finish();
707
						return;
708
					}
709
					$this->setWatermark($this->valueLength + 2);
710
					$this->state = static::STATE_BINARY; // binary data block
711
					break; // stop reading line-by-line
712
				}
713
			}
714
		}
715
716
		if ($this->state === static::STATE_BINARY) { // inside of binary data block
717
			if ($this->getInputLength() < $this->valueLength + 2) {
718
				return; //we do not have a whole packet
719
			}
720
			$value = $this->read($this->valueLength);
721
			if ($this->read(2) !== $this->EOL) {
722
				$this->finish();
723
				return;
724
			}
725
			$this->state = static::STATE_STANDBY;
726
			$this->setWatermark(3);
727
			$this->pushValue($value);
728
			goto start;
729
		}
730
	}
731
}
732