Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 48ae8e5

Browse files
Implement PUBSUB command
This commit implements the Redis PUBSUB command, a new command available since Redis 2.8.0 and described here: http://redis.io/commands/pubsub Addresses phpredis#427
1 parent 1a89ec2 commit 48ae8e5

File tree

5 files changed

+235
-1
lines changed

5 files changed

+235
-1
lines changed

README.markdown

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,6 +2864,7 @@ while($arr_matches = $redis->zscan('zset', $it, '*pattern*')) {
28642864
* [psubscribe](#psubscribe) - Subscribe to channels by pattern
28652865
* [publish](#publish) - Post a message to a channel
28662866
* [subscribe](#subscribe) - Subscribe to channels
2867+
* [pubsub](#pubsub) - Introspection into the pub/sub subsystem
28672868

28682869
### psubscribe
28692870
-----
@@ -2924,6 +2925,26 @@ function f($redis, $chan, $msg) {
29242925
$redis->subscribe(array('chan-1', 'chan-2', 'chan-3'), 'f'); // subscribe to 3 chans
29252926
~~~
29262927

2928+
### pubsub
2929+
-----
2930+
_**Description**_: A command allowing you to get information on the Redis pub/sub system.
2931+
2932+
##### *Parameters*
2933+
*keyword*: String, which can be: "channels", "numsub", or "numpat"
2934+
*argument*: Optional, variant. For the "channels" subcommand, you can pass a string pattern. For "numsub" an array of channel names.
2935+
2936+
##### *Return value*
2937+
*CHANNELS*: Returns an array where the members are the matching channels.
2938+
*NUMSUB*: Returns a key/value array where the keys are channel names and values are their counts.
2939+
*NUMPAT*: Integer return containing the number active pattern subscriptions
2940+
2941+
##### *Example*
2942+
~~~
2943+
$redis->pubsub("channels"); /*All channels */
2944+
$redis->pubsub("channels", "*pattern*"); /* Just channels matching your pattern */
2945+
$redis->pubsub("numsub", Array("chan1", "chan2")); /*Get subscriber counts for 'chan1' and 'chan2'*/
2946+
$redsi->pubsub("numpat"); /* Get the number of pattern subscribers */
2947+
```
29272948
29282949
## Transactions
29292950

common.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ typedef enum _REDIS_SCAN_TYPE {
4545
TYPE_ZSCAN
4646
} REDIS_SCAN_TYPE;
4747

48+
/* PUBSUB subcommands */
49+
typedef enum _PUBSUB_TYPE {
50+
PUBSUB_CHANNELS,
51+
PUBSUB_NUMSUB,
52+
PUBSUB_NUMPAT
53+
} PUBSUB_TYPE;
54+
4855
/* options */
4956
#define REDIS_OPT_SERIALIZER 1
5057
#define REDIS_OPT_PREFIX 2

php_redis.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ PHP_METHOD(Redis, setOption);
183183
PHP_METHOD(Redis, config);
184184
PHP_METHOD(Redis, slowlog);
185185
PHP_METHOD(Redis, wait);
186+
PHP_METHOD(Redis, pubsub);
186187

187188
PHP_METHOD(Redis, client);
188189

redis.c

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ static zend_function_entry redis_functions[] = {
276276
PHP_ME(Redis, isConnected, NULL, ZEND_ACC_PUBLIC)
277277

278278
PHP_ME(Redis, wait, NULL, ZEND_ACC_PUBLIC)
279+
PHP_ME(Redis, pubsub, NULL, ZEND_ACC_PUBLIC)
279280

280281
/* aliases */
281282
PHP_MALIAS(Redis, open, connect, NULL, ZEND_ACC_PUBLIC)
@@ -6077,6 +6078,169 @@ PHP_METHOD(Redis, wait) {
60776078
REDIS_PROCESS_RESPONSE(redis_long_response);
60786079
}
60796080

6081+
/*
6082+
* Construct a PUBSUB command
6083+
*/
6084+
PHPAPI int
6085+
redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type,
6086+
zval *arg TSRMLS_CC)
6087+
{
6088+
HashTable *ht_chan;
6089+
HashPosition ptr;
6090+
zval **z_ele;
6091+
char *key;
6092+
int cmd_len, key_len, key_free;
6093+
smart_str cmd = {0};
6094+
6095+
if(type == PUBSUB_CHANNELS) {
6096+
if(arg) {
6097+
// Get string argument and length.
6098+
key = Z_STRVAL_P(arg);
6099+
key_len = Z_STRLEN_P(arg);
6100+
6101+
// Prefix if necissary
6102+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
6103+
6104+
// With a pattern
6105+
cmd_len = redis_cmd_format_static(ret, "PUBSUB", "ss", "CHANNELS", sizeof("CHANNELS")-1,
6106+
key, key_len);
6107+
6108+
// Free the channel name if we prefixed it
6109+
if(key_free) efree(key);
6110+
6111+
// Return command length
6112+
return cmd_len;
6113+
} else {
6114+
// No pattern
6115+
return redis_cmd_format_static(ret, "PUBSUB", "s", "CHANNELS", sizeof("CHANNELS")-1);
6116+
}
6117+
} else if(type == PUBSUB_NUMSUB) {
6118+
ht_chan = Z_ARRVAL_P(arg);
6119+
6120+
// Add PUBSUB and NUMSUB bits
6121+
redis_cmd_init_sstr(&cmd, zend_hash_num_elements(ht_chan)+1, "PUBSUB", sizeof("PUBSUB")-1);
6122+
redis_cmd_append_sstr(&cmd, "NUMSUB", sizeof("NUMSUB")-1);
6123+
6124+
// Iterate our elements
6125+
for(zend_hash_internal_pointer_reset_ex(ht_chan, &ptr);
6126+
zend_hash_get_current_data_ex(ht_chan, (void**)&z_ele, &ptr)==SUCCESS;
6127+
zend_hash_move_forward_ex(ht_chan, &ptr))
6128+
{
6129+
char *key;
6130+
int key_len, key_free;
6131+
zval *z_tmp = NULL;
6132+
6133+
if(Z_TYPE_PP(z_ele) == IS_STRING) {
6134+
key = Z_STRVAL_PP(z_ele);
6135+
key_len = Z_STRLEN_PP(z_ele);
6136+
} else {
6137+
MAKE_STD_ZVAL(z_tmp);
6138+
*z_tmp = **z_ele;
6139+
zval_copy_ctor(z_tmp);
6140+
convert_to_string(z_tmp);
6141+
6142+
key = Z_STRVAL_P(z_tmp);
6143+
key_len = Z_STRLEN_P(z_tmp);
6144+
}
6145+
6146+
// Apply prefix if required
6147+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
6148+
6149+
// Append this channel
6150+
redis_cmd_append_sstr(&cmd, key, key_len);
6151+
6152+
// Free key if prefixed
6153+
if(key_free) efree(key);
6154+
6155+
// Free our temp var if we converted from something other than a string
6156+
if(z_tmp) {
6157+
zval_dtor(z_tmp);
6158+
efree(z_tmp);
6159+
z_tmp = NULL;
6160+
}
6161+
}
6162+
6163+
// Set return
6164+
*ret = cmd.c;
6165+
return cmd.len;
6166+
} else if(type == PUBSUB_NUMPAT) {
6167+
return redis_cmd_format_static(ret, "PUBSUB", "s", "NUMPAT", sizeof("NUMPAT")-1);
6168+
}
6169+
6170+
// Shouldn't ever happen
6171+
return -1;
6172+
}
6173+
6174+
/*
6175+
* {{{ proto Redis::pubsub("channels", pattern);
6176+
* proto Redis::pubsub("numsub", Array channels);
6177+
* proto Redis::pubsub("numpat"); }}}
6178+
*/
6179+
PHP_METHOD(Redis, pubsub) {
6180+
zval *object;
6181+
RedisSock *redis_sock;
6182+
char *keyword, *cmd;
6183+
int kw_len, cmd_len;
6184+
PUBSUB_TYPE type;
6185+
zval *arg=NULL;
6186+
6187+
// Parse arguments
6188+
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|z",
6189+
&object, redis_ce, &keyword, &kw_len, &arg)
6190+
==FAILURE)
6191+
{
6192+
RETURN_FALSE;
6193+
}
6194+
6195+
// Validate our sub command keyword, and that we've got proper arguments
6196+
if(!strncasecmp(keyword, "channels", sizeof("channels"))) {
6197+
// One (optional) string argument
6198+
if(arg && Z_TYPE_P(arg) != IS_STRING) {
6199+
RETURN_FALSE;
6200+
}
6201+
type = PUBSUB_CHANNELS;
6202+
} else if(!strncasecmp(keyword, "numsub", sizeof("numsub"))) {
6203+
// One array argument
6204+
if(ZEND_NUM_ARGS() < 2 || Z_TYPE_P(arg) != IS_ARRAY ||
6205+
zend_hash_num_elements(Z_ARRVAL_P(arg))==0)
6206+
{
6207+
RETURN_FALSE;
6208+
}
6209+
type = PUBSUB_NUMSUB;
6210+
} else if(!strncasecmp(keyword, "numpat", sizeof("numpat"))) {
6211+
type = PUBSUB_NUMPAT;
6212+
} else {
6213+
// Invalid keyword
6214+
RETURN_FALSE;
6215+
}
6216+
6217+
// Grab our socket context object
6218+
if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0)<0) {
6219+
RETURN_FALSE;
6220+
}
6221+
6222+
// Construct our "PUBSUB" command
6223+
cmd_len = redis_build_pubsub_cmd(redis_sock, &cmd, type, arg TSRMLS_CC);
6224+
6225+
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
6226+
6227+
if(type == PUBSUB_NUMSUB) {
6228+
IF_ATOMIC() {
6229+
if(redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) {
6230+
RETURN_FALSE;
6231+
}
6232+
}
6233+
REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped);
6234+
} else {
6235+
IF_ATOMIC() {
6236+
if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL)<0) {
6237+
RETURN_FALSE;
6238+
}
6239+
}
6240+
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
6241+
}
6242+
}
6243+
60806244
// Construct an EVAL or EVALSHA command, with option argument array and number of arguments that are keys parameter
60816245
PHPAPI int
60826246
redis_build_eval_cmd(RedisSock *redis_sock, char **ret, char *keyword, char *value, int val_len, zval *args, int keys_count TSRMLS_DC) {

tests/TestRedis.php

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,48 @@ public function testPipelinePublish() {
7070
->exec();
7171

7272
$this->assertTrue(is_array($ret) && count($ret) === 1 && $ret[0] >= 0);
73-
}
73+
}
74+
75+
// Run some simple tests against the PUBSUB command. This is problematic, as we
76+
// can't be sure what's going on in the instance, but we can do some things.
77+
public function testPubSub() {
78+
// Only available since 2.8.0
79+
if(version_compare($this->version, "2.8.0", "lt")) {
80+
$this->markTestSkipped();
81+
return;
82+
}
83+
84+
// PUBSUB CHANNELS ...
85+
$result = $this->redis->pubsub("channels", "*");
86+
$this->assertTrue(is_array($result));
87+
$result = $this->redis->pubsub("channels");
88+
$this->assertTrue(is_array($result));
89+
90+
// PUBSUB NUMSUB
91+
92+
$c1 = uniqid() . '-' . rand(1,100);
93+
$c2 = uniqid() . '-' . rand(1,100);
94+
95+
$result = $this->redis->pubsub("numsub", Array($c1, $c2));
96+
97+
// Should get an array back, with two elements
98+
$this->assertTrue(is_array($result));
99+
$this->assertEquals(count($result), 2);
100+
101+
// Make sure the elements are correct, and have zero counts
102+
foreach(Array($c1,$c2) as $channel) {
103+
$this->assertTrue(isset($result[$channel]));
104+
$this->assertEquals($result[$channel], "0");
105+
}
106+
107+
// PUBSUB NUMPAT
108+
$result = $this->redis->pubsub("numpat");
109+
$this->assertTrue(is_int($result));
110+
111+
// Invalid calls
112+
$this->assertFalse($this->redis->pubsub("notacommand"));
113+
$this->assertFalse($this->redis->pubsub("numsub", "not-an-array"));
114+
}
74115

75116
public function testBitsets() {
76117

0 commit comments

Comments
 (0)