From ca6050e85888faa33944e246f58c6ffb5780be0b Mon Sep 17 00:00:00 2001 From: DarkGhosthunter Date: Tue, 10 Aug 2021 00:14:08 -0400 Subject: [PATCH 1/4] Adds an attempt method w/tests. --- src/Illuminate/Cache/RateLimiter.php | 22 ++++++++++++++ .../Support/Facades/RateLimiter.php | 1 + tests/Cache/CacheRateLimiterTest.php | 30 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index 6b21d995a3ba..c3af6f5f5417 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -163,4 +163,26 @@ public function availableIn($key) { return max(0, $this->cache->get($key.':timer') - $this->currentTime()); } + + /** + * Attempts to execute a callback if it's available. + * + * @param string $key + * @param int $maxAttempts + * @param \Closure $callback + * @param int $decaySeconds + * @return bool + */ + public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) + { + if ($this->tooManyAttempts($key, $maxAttempts)) { + return false; + } + + $callback(); + + $this->hit($key, $decaySeconds); + + return true; + } } diff --git a/src/Illuminate/Support/Facades/RateLimiter.php b/src/Illuminate/Support/Facades/RateLimiter.php index 23b1a31e1538..5cfd78462fc2 100644 --- a/src/Illuminate/Support/Facades/RateLimiter.php +++ b/src/Illuminate/Support/Facades/RateLimiter.php @@ -12,6 +12,7 @@ * @method static int retriesLeft($key, $maxAttempts) * @method static void clear($key) * @method static int availableIn($key) + * @method static bool attempt($key, $maxAttempts, \Closure $callback, $decaySeconds = 60) * * @see \Illuminate\Cache\RateLimiter */ diff --git a/tests/Cache/CacheRateLimiterTest.php b/tests/Cache/CacheRateLimiterTest.php index ff37c89a9953..eb280f4b539b 100644 --- a/tests/Cache/CacheRateLimiterTest.php +++ b/tests/Cache/CacheRateLimiterTest.php @@ -76,4 +76,34 @@ public function testAvailableInReturnsPositiveValues() $this->assertTrue($rateLimiter->availableIn('key:timer') >= 0); $this->assertTrue($rateLimiter->availableIn('key:timer') >= 0); } + + public function testAttemptsCallbackReturnsTrue() + { + $cache = m::mock(Cache::class); + $cache->shouldReceive('get')->once()->with('key', 0)->andReturn(0); + $cache->shouldReceive('add')->once()->with('key:timer', m::type('int'), 1); + $cache->shouldReceive('add')->once()->with('key', 0, 1)->andReturns(1); + $cache->shouldReceive('increment')->once()->with('key')->andReturn(1); + + $executed = false; + + $rateLimiter = new RateLimiter($cache); + + $this->assertTrue($rateLimiter->attempt('key', 1, function() use (&$executed) { $executed = true; }, 1)); + $this->assertTrue($executed); + } + + public function testAttemptsCallbackReturnsFalse() + { + $cache = m::mock(Cache::class); + $cache->shouldReceive('get')->once()->with('key', 0)->andReturn(2); + $cache->shouldReceive('has')->once()->with('key:timer')->andReturn(true); + + $executed = false; + + $rateLimiter = new RateLimiter($cache); + + $this->assertFalse($rateLimiter->attempt('key', 1, function() use (&$executed) { $executed = true; }, 1)); + $this->assertFalse($executed); + } } From 2fc1a03747caef10f3cad2bd5e0f0db4d2ff657f Mon Sep 17 00:00:00 2001 From: DarkGhosthunter Date: Tue, 10 Aug 2021 00:32:05 -0400 Subject: [PATCH 2/4] Modified to return callback result. --- src/Illuminate/Cache/RateLimiter.php | 6 +++--- tests/Cache/CacheRateLimiterTest.php | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index c3af6f5f5417..4cbf2398b413 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -171,7 +171,7 @@ public function availableIn($key) * @param int $maxAttempts * @param \Closure $callback * @param int $decaySeconds - * @return bool + * @return mixed */ public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) { @@ -179,10 +179,10 @@ public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 6 return false; } - $callback(); + $result = $callback(); $this->hit($key, $decaySeconds); - return true; + return $result ?? true; } } diff --git a/tests/Cache/CacheRateLimiterTest.php b/tests/Cache/CacheRateLimiterTest.php index eb280f4b539b..3bcb0feab8e9 100644 --- a/tests/Cache/CacheRateLimiterTest.php +++ b/tests/Cache/CacheRateLimiterTest.php @@ -93,6 +93,21 @@ public function testAttemptsCallbackReturnsTrue() $this->assertTrue($executed); } + public function testAttemptsCallbackReturnsCallbackReturn() + { + $cache = m::mock(Cache::class); + $cache->shouldReceive('get')->once()->with('key', 0)->andReturn(0); + $cache->shouldReceive('add')->once()->with('key:timer', m::type('int'), 1); + $cache->shouldReceive('add')->once()->with('key', 0, 1)->andReturns(1); + $cache->shouldReceive('increment')->once()->with('key')->andReturn(1); + + $rateLimiter = new RateLimiter($cache); + + $this->assertEquals('foo', $rateLimiter->attempt('key', 1, function() { + return 'foo'; + }, 1)); + } + public function testAttemptsCallbackReturnsFalse() { $cache = m::mock(Cache::class); From 869f42636583394f3035fcad0c96cf72d4b5ca10 Mon Sep 17 00:00:00 2001 From: DarkGhosthunter Date: Tue, 10 Aug 2021 00:40:43 -0400 Subject: [PATCH 3/4] Style fixes. --- tests/Cache/CacheRateLimiterTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Cache/CacheRateLimiterTest.php b/tests/Cache/CacheRateLimiterTest.php index 3bcb0feab8e9..fe279efdbf70 100644 --- a/tests/Cache/CacheRateLimiterTest.php +++ b/tests/Cache/CacheRateLimiterTest.php @@ -89,7 +89,9 @@ public function testAttemptsCallbackReturnsTrue() $rateLimiter = new RateLimiter($cache); - $this->assertTrue($rateLimiter->attempt('key', 1, function() use (&$executed) { $executed = true; }, 1)); + $this->assertTrue($rateLimiter->attempt('key', 1, function() use (&$executed) { + $executed = true; + }, 1)); $this->assertTrue($executed); } @@ -118,7 +120,9 @@ public function testAttemptsCallbackReturnsFalse() $rateLimiter = new RateLimiter($cache); - $this->assertFalse($rateLimiter->attempt('key', 1, function() use (&$executed) { $executed = true; }, 1)); + $this->assertFalse($rateLimiter->attempt('key', 1, function() use (&$executed) { + $executed = true; + }, 1)); $this->assertFalse($executed); } } From 6757d4da6188eff0a7cadfa2bd43f5534a7b5247 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 10 Aug 2021 09:08:49 -0500 Subject: [PATCH 4/4] formatting --- src/Illuminate/Cache/RateLimiter.php | 42 +++++++++++++--------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index 4cbf2398b413..0778ebdeda12 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -60,6 +60,26 @@ public function limiter(string $name) return $this->limiters[$name] ?? null; } + /** + * Attempts to execute a callback if it's not limited. + * + * @param string $key + * @param int $maxAttempts + * @param \Closure $callback + * @param int $decaySeconds + * @return mixed + */ + public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) + { + if ($this->tooManyAttempts($key, $maxAttempts)) { + return false; + } + + return tap($callback() ?: true, function () use ($key, $decaySeconds) { + $this->hit($key, $decaySeconds); + }); + } + /** * Determine if the given key has been "accessed" too many times. * @@ -163,26 +183,4 @@ public function availableIn($key) { return max(0, $this->cache->get($key.':timer') - $this->currentTime()); } - - /** - * Attempts to execute a callback if it's available. - * - * @param string $key - * @param int $maxAttempts - * @param \Closure $callback - * @param int $decaySeconds - * @return mixed - */ - public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) - { - if ($this->tooManyAttempts($key, $maxAttempts)) { - return false; - } - - $result = $callback(); - - $this->hit($key, $decaySeconds); - - return $result ?? true; - } }