Skip to content

Commit ede74e8

Browse files
authored
Merge pull request #116 from webdcg/Scripting
Redis | Scripting | eval => Evaluate a LUA script serverside.
2 parents 5964898 + 35bb54b commit ede74e8

File tree

7 files changed

+583
-25
lines changed

7 files changed

+583
-25
lines changed

docs/scripting.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
|Command |Description |Supported |Tested |Class/Trait |Method |
55
|--- |--- |:-: |:-: |--- |--- |
6-
|[eval](#eval) |Evaluate a LUA script serverside. |:x: |:x: |Scripting |eval |
7-
|[evalSha](#evalSha) |Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself. |:x: |:x: |Scripting |evalSha |
8-
|[script](#script) |Execute the Redis SCRIPT command to perform various operations on the scripting subsystem. |:x: |:x: |Scripting |script |
9-
|[getLastError](#getLastError) |The last error message (if any). |:x: |:x: |Scripting |getLastError |
10-
|[clearLastError](#clearLastError) |Clear the last error message. |:x: |:x: |Scripting |clearLastError |
11-
|[\_prefix](#prefix) |A utility method to prefix the value with the prefix setting for phpredis. |:x: |:x: |Scripting |\_prefix |
12-
|[\_unserialize](#unserialize) |A utility method to unserialize data with whatever serializer is set up. |:x: |:x: |Scripting |\_unserialize |
13-
|[\_serialize](#serialize) |A utility method to serialize data with whatever serializer is set up. |:x: |:x: |Scripting |\_serialize |
6+
|[eval](#eval) |Evaluate a LUA script serverside. |:white\_check\_mark: |:white\_check\_mark: |Scripting |eval |
7+
|[evalSha](#evalSha) |Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself. |:white\_check\_mark: |:white\_check\_mark: |Scripting |evalSha |
8+
|[script](#script) |Execute the Redis SCRIPT command to perform various operations on the scripting subsystem. |:white\_check\_mark: |:white\_check\_mark: |Scripting |script |
9+
|[getLastError](#getLastError) |The last error message (if any). |:white\_check\_mark: |:white\_check\_mark: |Scripting |getLastError |
10+
|[clearLastError](#clearLastError) |Clear the last error message. |:white\_check\_mark: |:white\_check\_mark: |Scripting |clearLastError |
11+
|[\_prefix](#prefix) |A utility method to prefix the value with the prefix setting for phpredis. |:white\_check\_mark: |:white\_check\_mark: |Scripting |\_prefix |
12+
|[\_unserialize](#unserialize) |A utility method to unserialize data with whatever serializer is set up. |:white\_check\_mark: |:white\_check\_mark: |Scripting |\_unserialize |
13+
|[\_serialize](#serialize) |A utility method to serialize data with whatever serializer is set up. |:white\_check\_mark: |:white\_check\_mark: |Scripting |\_serialize |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Webdcg\Redis\Exceptions;
4+
5+
use Exception;
6+
7+
class ScriptCommandException extends Exception
8+
{
9+
}

src/Traits/Scripting.php

Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,186 @@
22

33
namespace Webdcg\Redis\Traits;
44

5+
use Webdcg\Redis\Exceptions\ScriptCommandException;
6+
57
trait Scripting
68
{
9+
/*
10+
* Available Script Commands
11+
*/
12+
protected $SCRIPT_COMMANDS = ['LOAD', 'FLUSH', 'KILL', 'EXISTS'];
13+
714
/**
815
* Evaluate a LUA script serverside.
16+
*
917
* See: https://redis.io/commands/eval.
1018
*
1119
* @param string $script
1220
* @param array $arguments
1321
* @param int|integer $numKeys
1422
*
15-
* @return mixed What is returned depends on what the LUA
16-
* script itself returns, which could be a scalar value
23+
* @return mixed What is returned depends on what the LUA script
24+
* itself returns, which could be a scalar value
1725
* (int/string), or an array. Arrays that are returned
1826
* can also contain other arrays, if that's how it was
1927
* set up in your LUA script. If there is an error
2028
* executing the LUA script, the getLastError()
2129
* function can tell you the message that came back
2230
* from Redis (e.g. compile error).
2331
*/
24-
public function eval(string $script, array $arguments = [], int $numKeys = 0)
32+
public function eval(string $script, ?array $arguments = null, ?int $numKeys = null)
2533
{
34+
if (!is_null($arguments) && !is_null($numKeys)) {
35+
return $this->redis->eval($script, $arguments, $numKeys);
36+
}
37+
2638
return $this->redis->eval($script);
2739
}
2840

29-
public function evalSha(): bool
41+
42+
/**
43+
* Evaluate a LUA script serverside, from the SHA1 hash of the script instead
44+
* of the script itself.
45+
*
46+
* In order to run this command Redis will have to have already loaded the
47+
* script, either by running it or via the SCRIPT LOAD command.
48+
*
49+
* See: https://redis.io/commands/evalsha.
50+
*
51+
* @param string $sha1 The sha1 encoded hash of the script you want to run.
52+
* @param array|null $arguments Arguments to pass to the LUA script.
53+
* @param int|null $numKeys The number of arguments that should go into the
54+
* KEYS array, vs. the ARGV array when Redis spins
55+
* the script (optional).
56+
*
57+
* @return mixed
58+
*/
59+
public function evalSha(string $sha1, ?array $arguments = null, ?int $numKeys = null)
3060
{
31-
return false;
61+
if (!is_null($arguments) && !is_null($numKeys)) {
62+
return $this->redis->evalSha($sha1, $arguments, $numKeys);
63+
}
64+
65+
return $this->redis->evalSha($sha1);
3266
}
3367

34-
public function script(): bool
68+
69+
/**
70+
* Execute the Redis SCRIPT command to perform various operations on the
71+
* scripting subsystem.
72+
*
73+
* See: https://redis.io/commands/script-load.
74+
* See: https://redis.io/commands/script-flush.
75+
*
76+
* @param string $command
77+
* @param splat $scripts
78+
*
79+
* @return mixed SCRIPT LOAD will return the SHA1 hash of the
80+
* passed script on success, and FALSE on
81+
* failure.
82+
* SCRIPT FLUSH should always return TRUE
83+
* SCRIPT KILL will return true if a script was
84+
* able to be killed and false if not.
85+
* SCRIPT EXISTS will return an array with TRUE
86+
* or FALSE for each passed script.
87+
*/
88+
public function script(string $command, ...$scripts)
3589
{
36-
return false;
90+
$command = strtoupper($command);
91+
92+
if (!in_array($command, $this->SCRIPT_COMMANDS)) {
93+
throw new ScriptCommandException('Script Command not supported', 1);
94+
}
95+
96+
if ($command == 'FLUSH' || $command == 'KILL') {
97+
return $this->redis->script($command);
98+
}
99+
100+
if ($command == 'EXISTS') {
101+
return $this->redis->script($command, ...$scripts);
102+
}
103+
104+
if (count($scripts) != 1) {
105+
throw new ScriptCommandException('Invalid Number of Scripts to Load', 1);
106+
}
107+
108+
return $this->redis->script($command, $scripts[0]);
37109
}
38110

39-
public function getLastError(): bool
111+
112+
/**
113+
* The last error message (if any)
114+
*
115+
*
116+
* @return mixed|string|null A string with the last returned script
117+
* based error message, or NULL if there
118+
* is no error.
119+
*/
120+
public function getLastError(): ?string
40121
{
41-
return false;
122+
return $this->redis->getLastError();
42123
}
43124

125+
126+
/**
127+
* Clear the last error message
128+
*
129+
* @return bool true
130+
*/
44131
public function clearLastError(): bool
45132
{
46-
return false;
133+
return $this->redis->clearLastError();
47134
}
48135

49-
public function prefix(): bool
136+
137+
/**
138+
* A utility method to prefix the value with the prefix setting for phpredis.
139+
*
140+
* @param string $key The value you wish to prefix
141+
*
142+
* @return string If a prefix is set up, the value now prefixed.
143+
* If there is no prefix, the value will be returned
144+
* unchanged.
145+
*/
146+
public function _prefix(string $key): string
50147
{
51-
return false;
148+
return $this->redis->_prefix($key);
52149
}
53150

54-
public function unserialize(): bool
151+
/**
152+
* A utility method to serialize values manually.
153+
*
154+
* This method allows you to serialize a value with whatever serializer is
155+
* configured, manually. This can be useful for serialization/unserialization
156+
* of data going in and out of EVAL commands as phpredis can't automatically
157+
* do this itself. Note that if no serializer is set, phpredis will change
158+
* Array values to 'Array', and Objects to 'Object'.
159+
*
160+
* @param mixed|string|array|obhect $value The value to be serialized.
161+
*
162+
* @return mixed The serialized value.
163+
*/
164+
public function _serialize($value)
55165
{
56-
return false;
166+
return $this->redis->_serialize($value);
57167
}
58168

59-
public function serialize(): bool
169+
170+
/**
171+
* A utility method to unserialize data with whatever serializer is set up.
172+
*
173+
* If there is no serializer set, the value will be returned unchanged.
174+
* If there is a serializer set up, and the data passed in is malformed,
175+
* an exception will be thrown. This can be useful if phpredis is
176+
* serializing values, and you return something from redis in a LUA script
177+
* that is serialized.
178+
*
179+
* @param string $value The value to be unserialized
180+
*
181+
* @return mixed Unserialized value
182+
*/
183+
public function _unserialize(string $value)
60184
{
61-
return false;
185+
return $this->redis->_unserialize($value);
62186
}
63187
}

tests/RedisScriptingTest.php

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,80 @@ protected function setUp(): void
2121
$this->keyOptional = 'Scripting:Optional';
2222
}
2323

24+
25+
/*
26+
* ========================================================================
27+
* script
28+
*
29+
* Redis | Scripting | _unserialize => A utility method to unserialize data with whatever serializer is set up.
30+
* ========================================================================
31+
*/
32+
33+
34+
/** @test */
35+
public function redis_Scripting__unserialize_PHP()
36+
{
37+
$this->redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP);
38+
$this->assertEquals('foo', $this->redis->_unserialize('s:3:"foo";')); // Returns 's:3:"foo";'
39+
$this->assertEquals([1, 2, 3], $this->redis->_unserialize('a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}')); // Returns 'a:0:{}'
40+
}
41+
42+
/** @test */
43+
public function redis_Scripting__serialize_PHP()
44+
{
45+
$this->redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP);
46+
$this->assertEquals('s:3:"foo";', $this->redis->_serialize('foo')); // Returns 's:3:"foo";'
47+
$this->assertEquals('a:0:{}', $this->redis->_serialize([])); // Returns 'a:0:{}'
48+
$this->assertEquals('O:8:"stdClass":0:{}', $this->redis->_serialize(new \stdClass())); // Returns 'O:8:"stdClass":0:{}'
49+
}
50+
51+
52+
/** @test */
53+
public function redis_Scripting__serialize_none()
54+
{
55+
$this->redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_NONE);
56+
$this->assertEquals('foo', $this->redis->_serialize('foo')); // returns "foo"
57+
$this->assertEquals('Array', $this->redis->_serialize([])); // Returns "Array"
58+
$this->assertEquals('Object', $this->redis->_serialize(new \stdClass())); // Returns "Object"
59+
}
60+
61+
62+
/** @test */
63+
public function redis_Scripting__prefix()
64+
{
65+
$this->redis->setOption(\Redis::OPT_PREFIX, 'tswift:');
66+
$prefix = $this->redis->_prefix('miss-americana');
67+
$this->assertEquals('tswift:miss-americana', $prefix);
68+
}
69+
70+
71+
/** @test */
72+
public function redis_Scripting_clearLastError()
73+
{
74+
$this->redis->eval('this-is-not-lua');
75+
$error = $this->redis->getLastError();
76+
$this->assertContains('ERR Error compiling script', $error);
77+
$clear = $this->redis->clearLastError();
78+
$this->assertTrue($clear);
79+
$this->assertIsBool($clear);
80+
$error = $this->redis->getLastError();
81+
$this->assertNull($error);
82+
}
83+
84+
85+
/** @test */
86+
public function redis_Scripting_getLastError()
87+
{
88+
$this->redis->eval('this-is-not-lua');
89+
$error = $this->redis->getLastError();
90+
$this->assertContains('ERR Error compiling script', $error);
91+
$clear = $this->redis->clearLastError();
92+
$this->assertTrue($clear);
93+
}
94+
95+
2496
/** @test */
25-
public function redis_Scripting_eval_simple()
97+
public function redis_Scripting_eval()
2698
{
2799
// Start from scratch
28100
$this->assertGreaterThanOrEqual(0, $this->redis->delete($this->key));

0 commit comments

Comments
 (0)