CacheThrottler   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Test Coverage

Coverage 82.5%

Importance

Changes 8
Bugs 2 Features 0
Metric Value
eloc 39
c 8
b 2
f 0
dl 0
loc 169
ccs 33
cts 40
cp 0.825
rs 10
wmc 13

9 Methods

Rating   Name   Duplication   Size   Complexity  
A hitRedis() 0 9 1
A __construct() 0 6 1
A getStore() 0 3 1
A clear() 0 7 1
A check() 0 3 1
A attempt() 0 7 1
A hit() 0 15 3
A computeRedisKey() 0 3 1
A count() 0 13 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Laravel Throttle.
7
 *
8
 * (c) Graham Campbell <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace GrahamCampbell\Throttle\Throttlers;
15
16
use Countable;
17
use Illuminate\Cache\RedisStore;
18
use Illuminate\Contracts\Cache\Store;
19
20
/**
21
 * This is the cache throttler class.
22
 *
23
 * @author Graham Campbell <[email protected]>
24
 */
25
class CacheThrottler implements ThrottlerInterface, Countable
26
{
27
    /**
28
     * The store instance.
29
     *
30
     * @var \Illuminate\Contracts\Cache\Store
31
     */
32
    protected $store;
33
34
    /**
35
     * The key.
36
     *
37
     * @var string
38
     */
39
    protected $key;
40
41
    /**
42
     * The request limit.
43
     *
44
     * @var int
45
     */
46
    protected $limit;
47
48
    /**
49
     * The expiration time.
50
     *
51
     * @var int
52
     */
53
    protected $time;
54
55
    /**
56
     * The number of requests.
57
     *
58
     * @var int
59
     */
60
    protected $number;
61
62
    /**
63
     * Create a new instance.
64
     *
65
     * @param \Illuminate\Contracts\Cache\Store $store
66
     * @param string                            $key
67
     * @param int                               $limit
68
     * @param int                               $time
69
     *
70
     * @return void
71
     */
72 168
    public function __construct(Store $store, string $key, int $limit, int $time)
73
    {
74 168
        $this->store = $store;
75 168
        $this->key = $key;
76 168
        $this->limit = $limit;
77 168
        $this->time = $time;
78 168
    }
79
80
    /**
81
     * Rate limit access to a resource.
82
     *
83
     * @return bool
84
     */
85 84
    public function attempt()
86
    {
87 84
        $response = $this->check();
88
89 84
        $this->hit();
90
91 84
        return $response;
92
    }
93
94
    /**
95
     * Hit the throttle.
96
     *
97
     * @return $this
98
     */
99 96
    public function hit()
100
    {
101 96
        if ($this->store instanceof RedisStore) {
102
            return $this->hitRedis();
103
        }
104
105 96
        if ($this->count()) {
106 72
            $this->store->increment($this->key);
107 72
            $this->number++;
108
        } else {
109 96
            $this->store->put($this->key, 1, LifetimeHelper::computeLifetime($this->time));
110 96
            $this->number = 1;
111
        }
112
113 96
        return $this;
114
    }
115
116
    /**
117
     * Clear the throttle.
118
     *
119
     * @return $this
120
     */
121 12
    public function clear()
122
    {
123 12
        $this->number = 0;
124
125 12
        $this->store->put($this->key, $this->number, LifetimeHelper::computeLifetime($this->time));
126
127 12
        return $this;
128
    }
129
130
    /**
131
     * Get the throttle hit count.
132
     *
133
     * @return int
134
     */
135 156
    public function count()
136
    {
137 156
        if ($this->number !== null) {
138 144
            return $this->number;
139
        }
140
141 144
        $this->number = (int) $this->store->get($this->key);
142
143 144
        if (!$this->number) {
144 108
            $this->number = 0;
145
        }
146
147 144
        return $this->number;
148
    }
149
150
    /**
151
     * Check the throttle.
152
     *
153
     * @return bool
154
     */
155 120
    public function check()
156
    {
157 120
        return $this->count() < $this->limit;
158
    }
159
160
    /**
161
     * Get the store instance.
162
     *
163
     * @return \Illuminate\Contracts\Cache\Store
164
     */
165 84
    public function getStore()
166
    {
167 84
        return $this->store;
168
    }
169
170
    /**
171
     * An atomic hit implementation for redis.
172
     *
173
     * @return $this
174
     */
175
    protected function hitRedis()
176
    {
177
        $lua = 'local v = redis.call(\'incr\', KEYS[1]) '.
178
               'if v>1 then return v '.
179
               'else redis.call(\'setex\', KEYS[1], ARGV[1], 1) return 1 end';
180
181
        $this->number = $this->store->connection()->eval($lua, 1, $this->computeRedisKey(), $this->time * 60);
182
183
        return $this;
184
    }
185
186
    /**
187
     * Compute the cache key for redis.
188
     *
189
     * @return string
190
     */
191
    protected function computeRedisKey()
192
    {
193
        return $this->store->getPrefix().$this->key;
194
    }
195
}
196