Skip to content

Commit 60e3ba7

Browse files
Merge branch 'feature/new_set_args' into develop
2 parents 8e81c49 + 711f053 commit 60e3ba7

File tree

4 files changed

+217
-77
lines changed

4 files changed

+217
-77
lines changed

README.markdown

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,19 +635,30 @@ $redis->get('key');
635635

636636
### set
637637
-----
638-
_**Description**_: Set the string value in argument as value of the key.
638+
_**Description**_: Set the string value in argument as value of the key. If you're using Redis >= 2.6.12, you can pass extended options as explained below
639639

640640
##### *Parameters*
641641
*Key*
642642
*Value*
643-
*Timeout* (optional). Calling `SETEX` is preferred if you want a timeout.
643+
*Timeout or Options Array* (optional). If you pass an integer, phpredis will redirect to SETEX, and will try to use Redis >= 2.6.12 extended options if you pass an array with valid values
644644

645645
##### *Return value*
646646
*Bool* `TRUE` if the command is successful.
647647

648648
##### *Examples*
649649
~~~
650+
// Simple key -> value set
650651
$redis->set('key', 'value');
652+
653+
// Will redirect, and actually make an SETEX call
654+
$redis->set('key','value', 10);
655+
656+
// Will set the key, if it doesn't exist, with a ttl of 10 seconds
657+
$redis->set('key', 'value', Array('nx', 'ex'=>10);
658+
659+
// Will set a key, if it does exist, with a ttl of 1000 miliseconds
660+
$redis->set('key', 'value', Array('xx', 'px'=>1000);
661+
651662
~~~
652663

653664
### setex, psetex

common.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
#ifndef REDIS_COMMON_H
66
#define REDIS_COMMON_H
77

8+
/* NULL check so Eclipse doesn't go crazy */
9+
#ifndef NULL
10+
#define NULL ((void *) 0)
11+
#endif
12+
813
#define redis_sock_name "Redis Socket Buffer"
914
#define REDIS_SOCK_STATUS_FAILED 0
1015
#define REDIS_SOCK_STATUS_DISCONNECTED 1
@@ -138,6 +143,15 @@ else if(redis_sock->mode == MULTI) { \
138143

139144
#define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL)
140145

146+
/* Extended SET argument detection */
147+
#define IS_EX_ARG(a) ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
148+
#define IS_PX_ARG(a) ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
149+
#define IS_NX_ARG(a) ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
150+
#define IS_XX_ARG(a) ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
151+
152+
#define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a))
153+
#define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a))
154+
141155
typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode;
142156

143157
typedef struct fold_item {

redis.c

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -804,43 +804,103 @@ PHP_METHOD(Redis, close)
804804
}
805805
/* }}} */
806806

807-
/* {{{ proto boolean Redis::set(string key, mixed value)
808-
*/
809-
PHP_METHOD(Redis, set)
810-
{
807+
/* {{{ proto boolean Redis::set(string key, mixed value, long timeout | array options) */
808+
PHP_METHOD(Redis, set) {
811809
zval *object;
812810
RedisSock *redis_sock;
813-
char *key = NULL, *val = NULL, *cmd;
811+
char *key = NULL, *val = NULL, *cmd, *exp_type = NULL, *set_type = NULL;
814812
int key_len, val_len, cmd_len;
815813
long expire = -1;
816814
int val_free = 0, key_free = 0;
817-
zval *z_value;
815+
zval *z_value, *z_opts = NULL;
818816

819-
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|l",
820-
&object, redis_ce, &key, &key_len,
821-
&z_value, &expire) == FAILURE) {
817+
// Make sure the arguments are correct
818+
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|z",
819+
&object, redis_ce, &key, &key_len, &z_value,
820+
&z_opts) == FAILURE)
821+
{
822822
RETURN_FALSE;
823823
}
824824

825-
if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
825+
// Ensure we can grab our redis socket
826+
if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
826827
RETURN_FALSE;
827828
}
828829

830+
/* Our optional argument can either be a long (to support legacy SETEX */
831+
/* redirection), or an array with Redis >= 2.6.12 set options */
832+
if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY) {
833+
RETURN_FALSE;
834+
}
835+
836+
/* Serialization, key prefixing */
829837
val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC);
830-
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
831-
if(expire > 0) {
832-
cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len, expire, val, val_len);
838+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
839+
840+
if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
841+
HashTable *kt = Z_ARRVAL_P(z_opts);
842+
int type;
843+
unsigned int ht_key_len;
844+
unsigned long idx;
845+
char *k;
846+
zval **v;
847+
848+
/* Iterate our option array */
849+
for(zend_hash_internal_pointer_reset(kt);
850+
zend_hash_has_more_elements(kt) == SUCCESS;
851+
zend_hash_move_forward(kt))
852+
{
853+
// Grab key and value
854+
type = zend_hash_get_current_key_ex(kt, &k, &ht_key_len, &idx, 0, NULL);
855+
zend_hash_get_current_data(kt, (void**)&v);
856+
857+
if(type == HASH_KEY_IS_STRING && (Z_TYPE_PP(v) == IS_LONG) &&
858+
(Z_LVAL_PP(v) > 0) && IS_EX_PX_ARG(k))
859+
{
860+
exp_type = k;
861+
expire = Z_LVAL_PP(v);
862+
} else if(Z_TYPE_PP(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_PP(v))) {
863+
set_type = Z_STRVAL_PP(v);
864+
}
865+
}
866+
} else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) {
867+
expire = Z_LVAL_P(z_opts);
868+
}
869+
870+
/* Now let's construct the command we want */
871+
if(exp_type && set_type) {
872+
/* SET <key> <value> NX|XX PX|EX <timeout> */
873+
cmd_len = redis_cmd_format_static(&cmd, "SET", "ssssl", key, key_len,
874+
val, val_len, set_type, 2, exp_type,
875+
2, expire);
876+
} else if(exp_type) {
877+
/* SET <key> <value> PX|EX <timeout> */
878+
cmd_len = redis_cmd_format_static(&cmd, "SET", "sssl", key, key_len,
879+
val, val_len, exp_type, 2, expire);
880+
} else if(set_type) {
881+
/* SET <key> <value> NX|XX */
882+
cmd_len = redis_cmd_format_static(&cmd, "SET", "sss", key, key_len,
883+
val, val_len, set_type, 2);
884+
} else if(expire > 0) {
885+
/* Backward compatible SETEX redirection */
886+
cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len,
887+
expire, val, val_len);
833888
} else {
834-
cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len, val, val_len);
889+
/* SET <key> <value> */
890+
cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len,
891+
val, val_len);
835892
}
836-
if(val_free) efree(val);
893+
894+
/* Free our key or value if we prefixed/serialized */
837895
if(key_free) efree(key);
896+
if(val_free) efree(val);
838897

839-
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
840-
IF_ATOMIC() {
841-
redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
842-
}
843-
REDIS_PROCESS_RESPONSE(redis_boolean_response);
898+
/* Kick off the command */
899+
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
900+
IF_ATOMIC() {
901+
redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
902+
}
903+
REDIS_PROCESS_RESPONSE(redis_boolean_response);
844904
}
845905

846906
PHPAPI void redis_generic_setex(INTERNAL_FUNCTION_PARAMETERS, char *keyword) {
@@ -2359,7 +2419,6 @@ PHP_METHOD(Redis, sMembers)
23592419
}
23602420
/* }}} */
23612421

2362-
23632422
PHPAPI int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len,
23642423
int min_argc, RedisSock **out_sock, int has_timeout, int all_keys, int can_serialize)
23652424
{

tests/TestRedis.php

Lines changed: 110 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -138,84 +138,140 @@ public function testErr() {
138138

139139
public function testSet()
140140
{
141-
$this->assertEquals(TRUE, $this->redis->set('key', 'nil'));
142-
$this->assertEquals('nil', $this->redis->get('key'));
141+
$this->assertEquals(TRUE, $this->redis->set('key', 'nil'));
142+
$this->assertEquals('nil', $this->redis->get('key'));
143143

144-
$this->assertEquals(TRUE, $this->redis->set('key', 'val'));
144+
$this->assertEquals(TRUE, $this->redis->set('key', 'val'));
145145

146-
$this->assertEquals('val', $this->redis->get('key'));
147-
$this->assertEquals('val', $this->redis->get('key'));
148-
$this->redis->delete('keyNotExist');
149-
$this->assertEquals(FALSE, $this->redis->get('keyNotExist'));
146+
$this->assertEquals('val', $this->redis->get('key'));
147+
$this->assertEquals('val', $this->redis->get('key'));
148+
$this->redis->delete('keyNotExist');
149+
$this->assertEquals(FALSE, $this->redis->get('keyNotExist'));
150150

151-
$this->redis->set('key2', 'val');
152-
$this->assertEquals('val', $this->redis->get('key2'));
151+
$this->redis->set('key2', 'val');
152+
$this->assertEquals('val', $this->redis->get('key2'));
153153

154-
$value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
155-
$this->redis->set('key2', $value);
156-
$this->assertEquals($value, $this->redis->get('key2'));
157-
$this->assertEquals($value, $this->redis->get('key2'));
154+
$value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
155+
156+
$this->redis->set('key2', $value);
157+
$this->assertEquals($value, $this->redis->get('key2'));
158+
$this->assertEquals($value, $this->redis->get('key2'));
158159

159-
$this->redis->delete('key');
160-
$this->redis->delete('key2');
160+
$this->redis->delete('key');
161+
$this->redis->delete('key2');
161162

162163

163-
$i = 66000;
164-
$value2 = 'X';
165-
while($i--) {
166-
$value2 .= 'A';
167-
}
168-
$value2 .= 'X';
164+
$i = 66000;
165+
$value2 = 'X';
166+
while($i--) {
167+
$value2 .= 'A';
168+
}
169+
$value2 .= 'X';
169170

170-
$this->redis->set('key', $value2);
171+
$this->redis->set('key', $value2);
171172
$this->assertEquals($value2, $this->redis->get('key'));
172-
$this->redis->delete('key');
173-
$this->assertEquals(False, $this->redis->get('key'));
173+
$this->redis->delete('key');
174+
$this->assertEquals(False, $this->redis->get('key'));
174175

175-
$data = gzcompress('42');
176+
$data = gzcompress('42');
176177
$this->assertEquals(True, $this->redis->set('key', $data));
177-
$this->assertEquals('42', gzuncompress($this->redis->get('key')));
178+
$this->assertEquals('42', gzuncompress($this->redis->get('key')));
178179

179-
$this->redis->delete('key');
180-
$data = gzcompress('value1');
180+
$this->redis->delete('key');
181+
$data = gzcompress('value1');
181182
$this->assertEquals(True, $this->redis->set('key', $data));
182-
$this->assertEquals('value1', gzuncompress($this->redis->get('key')));
183-
184-
$this->redis->delete('key');
185-
$this->assertEquals(TRUE, $this->redis->set('key', 0));
186-
$this->assertEquals('0', $this->redis->get('key'));
187-
$this->assertEquals(TRUE, $this->redis->set('key', 1));
188-
$this->assertEquals('1', $this->redis->get('key'));
189-
$this->assertEquals(TRUE, $this->redis->set('key', 0.1));
190-
$this->assertEquals('0.1', $this->redis->get('key'));
191-
$this->assertEquals(TRUE, $this->redis->set('key', '0.1'));
192-
$this->assertEquals('0.1', $this->redis->get('key'));
193-
$this->assertEquals(TRUE, $this->redis->set('key', TRUE));
194-
$this->assertEquals('1', $this->redis->get('key'));
195-
196-
$this->assertEquals(True, $this->redis->set('key', ''));
197-
$this->assertEquals('', $this->redis->get('key'));
198-
$this->assertEquals(True, $this->redis->set('key', NULL));
199-
$this->assertEquals('', $this->redis->get('key'));
183+
$this->assertEquals('value1', gzuncompress($this->redis->get('key')));
184+
185+
$this->redis->delete('key');
186+
$this->assertEquals(TRUE, $this->redis->set('key', 0));
187+
$this->assertEquals('0', $this->redis->get('key'));
188+
$this->assertEquals(TRUE, $this->redis->set('key', 1));
189+
$this->assertEquals('1', $this->redis->get('key'));
190+
$this->assertEquals(TRUE, $this->redis->set('key', 0.1));
191+
$this->assertEquals('0.1', $this->redis->get('key'));
192+
$this->assertEquals(TRUE, $this->redis->set('key', '0.1'));
193+
$this->assertEquals('0.1', $this->redis->get('key'));
194+
$this->assertEquals(TRUE, $this->redis->set('key', TRUE));
195+
$this->assertEquals('1', $this->redis->get('key'));
196+
197+
$this->assertEquals(True, $this->redis->set('key', ''));
198+
$this->assertEquals('', $this->redis->get('key'));
199+
$this->assertEquals(True, $this->redis->set('key', NULL));
200+
$this->assertEquals('', $this->redis->get('key'));
200201

201202
$this->assertEquals(True, $this->redis->set('key', gzcompress('42')));
202203
$this->assertEquals('42', gzuncompress($this->redis->get('key')));
204+
}
205+
206+
/* Extended SET options for Redis >= 2.6.12 */
207+
public function testExtendedSet() {
208+
// Skip the test if we don't have a new enough version of Redis
209+
if(version_compare($this->version, '2.6.12', 'lt')) {
210+
$this->markTestSkipped();
211+
return;
212+
}
203213

214+
/* Legacy SETEX redirection */
215+
$this->redis->del('foo');
216+
$this->assertTrue($this->redis->set('foo','bar', 20));
217+
$this->assertEquals($this->redis->get('foo'), 'bar');
218+
$this->assertEquals($this->redis->ttl('foo'), 20);
219+
220+
/* Invalid third arguments */
221+
$this->assertFalse($this->redis->set('foo','bar','baz'));
222+
$this->assertFalse($this->redis->set('foo','bar',new StdClass()));
223+
224+
/* Set if not exist */
225+
$this->redis->del('foo');
226+
$this->assertTrue($this->redis->set('foo','bar',Array('nx')));
227+
$this->assertEquals($this->redis->get('foo'), 'bar');
228+
$this->assertFalse($this->redis->set('foo','bar',Array('nx')));
229+
230+
/* Set if exists */
231+
$this->assertTrue($this->redis->set('foo','bar',Array('xx')));
232+
$this->assertEquals($this->redis->get('foo'), 'bar');
233+
$this->redis->del('foo');
234+
$this->assertFalse($this->redis->set('foo','bar',Array('xx')));
235+
236+
/* Set with a TTL */
237+
$this->assertTrue($this->redis->set('foo','bar',Array('ex'=>100)));
238+
$this->assertEquals($this->redis->ttl('foo'), 100);
239+
240+
/* Set with a PTTL */
241+
$this->assertTrue($this->redis->set('foo','bar',Array('px'=>100000)));
242+
$this->assertTrue(100000 - $this->redis->pttl('foo') < 1000);
243+
244+
/* Set if exists, with a TTL */
245+
$this->assertTrue($this->redis->set('foo','bar',Array('xx','ex'=>105)));
246+
$this->assertEquals($this->redis->ttl('foo'), 105);
247+
$this->assertEquals($this->redis->get('foo'), 'bar');
248+
249+
/* Set if not exists, with a TTL */
250+
$this->redis->del('foo');
251+
$this->assertTrue($this->redis->set('foo','bar', Array('nx', 'ex'=>110)));
252+
$this->assertEquals($this->redis->ttl('foo'), 110);
253+
$this->assertEquals($this->redis->get('foo'), 'bar');
254+
$this->assertFalse($this->redis->set('foo','bar', Array('nx', 'ex'=>110)));
255+
256+
/* Throw some nonsense into the array, and check that the TTL came through */
257+
$this->redis->del('foo');
258+
$this->assertTrue($this->redis->set('foo','barbaz', Array('not-valid','nx','invalid','ex'=>200)));
259+
$this->assertEquals($this->redis->ttl('foo'), 200);
260+
$this->assertEquals($this->redis->get('foo'), 'barbaz');
204261
}
205-
public function testGetSet() {
206262

207-
$this->redis->delete('key');
208-
$this->assertTrue($this->redis->getSet('key', '42') === FALSE);
209-
$this->assertTrue($this->redis->getSet('key', '123') === '42');
210-
$this->assertTrue($this->redis->getSet('key', '123') === '123');
263+
public function testGetSet() {
264+
$this->redis->delete('key');
265+
$this->assertTrue($this->redis->getSet('key', '42') === FALSE);
266+
$this->assertTrue($this->redis->getSet('key', '123') === '42');
267+
$this->assertTrue($this->redis->getSet('key', '123') === '123');
211268
}
212269

213270
public function testRandomKey() {
214-
215271
for($i = 0; $i < 1000; $i++) {
216272
$k = $this->redis->randomKey();
217-
$this->assertTrue($this->redis->exists($k));
218-
}
273+
$this->assertTrue($this->redis->exists($k));
274+
}
219275
}
220276

221277
public function testRename() {

0 commit comments

Comments
 (0)