RedisStore::executeLua()   A
last analyzed

Complexity

Conditions 6
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 16
rs 9.2222
ccs 9
cts 9
cp 1
cc 6
nc 3
nop 3
crap 6
1
<?php
2
3
namespace Zenstruck\Governator\Store;
4
5
use Predis\ClientInterface;
6
use Symfony\Component\Cache\Traits\RedisClusterProxy;
7
use Symfony\Component\Cache\Traits\RedisProxy;
8
use Zenstruck\Governator\Counter;
9
use Zenstruck\Governator\Key;
10
use Zenstruck\Governator\Store;
11
12
/**
13
 * @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Lock/Store/RedisStore.php
14
 *
15
 * @author Jérémy Derussé <[email protected]>
16
 * @author Kevin Bond <[email protected]>
17
 */
18
final class RedisStore implements Store
19
{
20
    /**
21
     * The Lua script for acquiring a lock.
22
     *
23
     * @see https://github.com/laravel/framework/blob/6dee0732994fd1c03762f6f18dc02a630489fd43/src/Illuminate/Redis/Limiters/DurationLimiter.php#L125
24
     *
25
     * KEYS[1] - The limiter name
26
     * ARGV[1] - Current time in microseconds
27
     * ARGV[2] - Current time in seconds
28
     * ARGV[3] - Duration of the bucket
29
     * ARGV[4] - Allowed number of tasks
30
     */
31
    private const LUA_HIT = "
32
        -- reset the bucket
33
        local function reset()
34
            redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
35
            redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
36
        end
37
38
        if redis.call('EXISTS', KEYS[1]) == 0 then
39
            -- if key does not exist, reset and return default counter
40
            reset()
41
            return {1, ARGV[2] + ARGV[3]}
42
        end
43
44
        if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
45
            -- call within the window, increase count and return counter
46
            redis.call('HINCRBY', KEYS[1], 'count', 1)
47
            return redis.call('HMGET', KEYS[1], 'count', 'end')
48
        end
49
50
        -- call not within window, reset counter and return default counter
51
        reset()
52
        return {1, ARGV[2] + ARGV[3]}
53
    ";
54
55
    /**
56
     * The Lua script for getting the "status" of a lock.
57
     *
58
     * KEYS[1] - The limiter name
59
     * ARGV[1] - Default resets at timestamp
60
     */
61
    private const LUA_STATUS = "
62
        if redis.call('EXISTS', KEYS[1]) == 0 then
63
            return {0, ARGV[1]}
64
        end
65
        return redis.call('HMGET', KEYS[1], 'count', 'end')
66
    ";
67
68
    private $client;
69
70
    /**
71
     * @param \Redis|\RedisArray|\RedisCluser|ClientInterface $client
0 ignored issues
show
Bug introduced by
The type RedisCluser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
72
     */
73 74
    public function __construct(object $client)
74
    {
75 74
        if (!$client instanceof \Redis && !$client instanceof \RedisArray && !$client instanceof \RedisCluster && !$client instanceof ClientInterface && !$client instanceof RedisProxy && !$client instanceof RedisClusterProxy) {
76 1
            throw new \InvalidArgumentException(\sprintf('"%s()" expects parameter 1 to be \Redis, \RedisArray, \RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, \is_object($client) ? \get_class($client) : \gettype($client)));
77
        }
78
79 73
        $this->client = $client;
80 73
    }
81
82 60
    public function hit(Key $key): Counter
83
    {
84 60
        $results = $this->executeLua(
85 60
            self::LUA_HIT,
86
            $key,
87 60
            (string) $key,
88 60
            microtime(true),
89 60
            time(),
90 60
            $key->ttl(),
91 60
            $key->limit()
92
        );
93
94 60
        return new Counter(...$results);
95
    }
96
97 18
    public function status(Key $key): Counter
98
    {
99 18
        $results = $this->executeLua(
100 18
            self::LUA_STATUS,
101
            $key,
102 18
            (string) $key,
103 18
            $key->createCounter()->resetsAt()
104
        );
105
106 18
        return new Counter(...$results);
107
    }
108
109 66
    public function reset(Key $key): void
110
    {
111 66
        $this->client->del((string) $key);
112 66
    }
113
114 66
    private function executeLua(string $script, string $key, ...$args): array
115
    {
116
        if (
117 66
            $this->client instanceof \Redis ||
118 55
            $this->client instanceof \RedisCluster ||
119 44
            $this->client instanceof RedisProxy ||
120 66
            $this->client instanceof RedisClusterProxy
121
        ) {
122 44
            return $this->client->eval($script, $args, 1);
123
        }
124
125 22
        if ($this->client instanceof \RedisArray) {
126 11
            return $this->client->_instance($this->client->_target($key))->eval($script, $args, 1);
0 ignored issues
show
Bug introduced by
The method _instance() does not exist on RedisArray. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

126
            return $this->client->/** @scrutinizer ignore-call */ _instance($this->client->_target($key))->eval($script, $args, 1);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
127
        }
128
129 11
        return $this->client->eval($script, 1, ...$args);
130
    }
131
}
132