Skip to content

Commit

Permalink
[11.x] Allows Unit & Backed Enums for registering named RateLimiter
Browse files Browse the repository at this point in the history
… & `RateLimited` middleware (#52935)

* added tests

* Update RateLimiter.php

* Update RateLimited.php

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
sethsandaru and taylorotwell committed Sep 27, 2024
1 parent 28ff0e3 commit ead6020
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 8 deletions.
33 changes: 27 additions & 6 deletions src/Illuminate/Cache/RateLimiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Illuminate\Cache;

use BackedEnum;
use Closure;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Support\InteractsWithTime;
use UnitEnum;

class RateLimiter
{
Expand Down Expand Up @@ -38,26 +40,30 @@ public function __construct(Cache $cache)
/**
* Register a named limiter configuration.
*
* @param string $name
* @param \BackedEnum|\UnitEnum|string $name
* @param \Closure $callback
* @return $this
*/
public function for(string $name, Closure $callback)
public function for($name, Closure $callback)
{
$this->limiters[$name] = $callback;
$resolvedName = $this->resolveLimiterName($name);

$this->limiters[$resolvedName] = $callback;

return $this;
}

/**
* Get the given named rate limiter.
*
* @param string $name
* @param \BackedEnum|\UnitEnum|string $name
* @return \Closure|null
*/
public function limiter(string $name)
public function limiter($name)
{
return $this->limiters[$name] ?? null;
$resolvedName = $this->resolveLimiterName($name);

return $this->limiters[$resolvedName] ?? null;
}

/**
Expand Down Expand Up @@ -248,4 +254,19 @@ public function cleanRateLimiterKey($key)
{
return preg_replace('/&([a-z])[a-z]+;/i', '$1', htmlentities($key));
}

/**
* Resolve the rate limiter name
*
* @param \BackedEnum|\UnitEnum|string $name
* @return string
*/
private function resolveLimiterName($name): string
{
return match (true) {
$name instanceof BackedEnum => $name->value,
$name instanceof UnitEnum => $name->name,
default => (string) $name,
};
}
}
10 changes: 8 additions & 2 deletions src/Illuminate/Queue/Middleware/RateLimited.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace Illuminate\Queue\Middleware;

use BackedEnum;
use Illuminate\Cache\RateLimiter;
use Illuminate\Cache\RateLimiting\Unlimited;
use Illuminate\Container\Container;
use Illuminate\Support\Arr;
use UnitEnum;

class RateLimited
{
Expand Down Expand Up @@ -33,14 +35,18 @@ class RateLimited
/**
* Create a new middleware instance.
*
* @param string $limiterName
* @param \BackedEnum|\UnitEnum|string $limiterName
* @return void
*/
public function __construct($limiterName)
{
$this->limiter = Container::getInstance()->make(RateLimiter::class);

$this->limiterName = $limiterName;
$this->limiterName = match (true) {
$limiterName instanceof BackedEnum => $limiterName->value,
$limiterName instanceof UnitEnum => $limiterName->name,
default => (string) $limiterName,
};
}

/**
Expand Down
51 changes: 51 additions & 0 deletions tests/Cache/RateLimiterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Illuminate\Tests\Cache;

use Illuminate\Cache\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Contracts\Cache\Repository as Cache;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;

class RateLimiterTest extends TestCase
{
public static function registerNamedRateLimiterDataProvider(): array
{
return [
'uses BackedEnum' => [BackedEnumNamedRateLimiter::API, 'api'],
'uses UnitEnum' => [UnitEnumNamedRateLimiter::THIRD_PARTY, 'THIRD_PARTY'],
'uses normal string' => ['yolo', 'yolo'],
'uses int' => [100, '100'],
];
}

#[DataProvider('registerNamedRateLimiterDataProvider')]
public function testRegisterNamedRateLimiter(mixed $name, string $expected): void
{
$reflectedLimitersProperty = new ReflectionProperty(RateLimiter::class, 'limiters');
$reflectedLimitersProperty->setAccessible(true);

$rateLimiter = new RateLimiter($this->createMock(Cache::class));
$rateLimiter->for($name, fn () => Limit::perMinute(100));

$limiters = $reflectedLimitersProperty->getValue($rateLimiter);

$this->assertArrayHasKey($expected, $limiters);

$limiterClosure = $rateLimiter->limiter($name);

$this->assertNotNull($limiterClosure);
}
}

enum BackedEnumNamedRateLimiter: string
{
case API = 'api';
}

enum UnitEnumNamedRateLimiter
{
case THIRD_PARTY;
}
68 changes: 68 additions & 0 deletions tests/Integration/Queue/RateLimitedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ public function testUnlimitedJobsAreExecuted()
$this->assertJobRanSuccessfully(RateLimitedTestJob::class);
}

public function testUnlimitedJobsAreExecutedUsingBackedEnum()
{
$rateLimiter = $this->app->make(RateLimiter::class);

$rateLimiter->for(BackedEnumNamedRateLimited::FOO, function ($job) {
return Limit::none();
});

$this->assertJobRanSuccessfully(RateLimitedTestJobUsingBackedEnum::class);
$this->assertJobRanSuccessfully(RateLimitedTestJobUsingBackedEnum::class);
}

public function testUnlimitedJobsAreExecutedUsingUnitEnum()
{
$rateLimiter = $this->app->make(RateLimiter::class);

$rateLimiter->for(UnitEnumNamedRateLimited::LARAVEL, function ($job) {
return Limit::none();
});

$this->assertJobRanSuccessfully(RateLimitedTestJobUsingUnitEnum::class);
$this->assertJobRanSuccessfully(RateLimitedTestJobUsingUnitEnum::class);
}

public function testRateLimitedJobsAreNotExecutedOnLimitReached2()
{
$cache = m::mock(Cache::class);
Expand Down Expand Up @@ -315,3 +339,47 @@ public function middleware()
return [(new RateLimited('test'))->dontRelease()];
}
}

enum BackedEnumNamedRateLimited: string
{
case FOO = 'bar';
}

enum UnitEnumNamedRateLimited
{
case LARAVEL;
}

class RateLimitedTestJobUsingBackedEnum
{
use InteractsWithQueue, Queueable;

public static $handled = false;

public function handle()
{
static::$handled = true;
}

public function middleware()
{
return [new RateLimited(BackedEnumNamedRateLimited::FOO)];
}
}

class RateLimitedTestJobUsingUnitEnum
{
use InteractsWithQueue, Queueable;

public static $handled = false;

public function handle()
{
static::$handled = true;
}

public function middleware()
{
return [new RateLimited(UnitEnumNamedRateLimited::LARAVEL)];
}
}

0 comments on commit ead6020

Please sign in to comment.