Skip to content

Commit ead6020

Browse files
[11.x] Allows Unit & Backed Enums for registering named RateLimiter & RateLimited middleware (#52935)
* added tests * Update RateLimiter.php * Update RateLimited.php --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 28ff0e3 commit ead6020

File tree

4 files changed

+154
-8
lines changed

4 files changed

+154
-8
lines changed

src/Illuminate/Cache/RateLimiter.php

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace Illuminate\Cache;
44

5+
use BackedEnum;
56
use Closure;
67
use Illuminate\Contracts\Cache\Repository as Cache;
78
use Illuminate\Support\InteractsWithTime;
9+
use UnitEnum;
810

911
class RateLimiter
1012
{
@@ -38,26 +40,30 @@ public function __construct(Cache $cache)
3840
/**
3941
* Register a named limiter configuration.
4042
*
41-
* @param string $name
43+
* @param \BackedEnum|\UnitEnum|string $name
4244
* @param \Closure $callback
4345
* @return $this
4446
*/
45-
public function for(string $name, Closure $callback)
47+
public function for($name, Closure $callback)
4648
{
47-
$this->limiters[$name] = $callback;
49+
$resolvedName = $this->resolveLimiterName($name);
50+
51+
$this->limiters[$resolvedName] = $callback;
4852

4953
return $this;
5054
}
5155

5256
/**
5357
* Get the given named rate limiter.
5458
*
55-
* @param string $name
59+
* @param \BackedEnum|\UnitEnum|string $name
5660
* @return \Closure|null
5761
*/
58-
public function limiter(string $name)
62+
public function limiter($name)
5963
{
60-
return $this->limiters[$name] ?? null;
64+
$resolvedName = $this->resolveLimiterName($name);
65+
66+
return $this->limiters[$resolvedName] ?? null;
6167
}
6268

6369
/**
@@ -248,4 +254,19 @@ public function cleanRateLimiterKey($key)
248254
{
249255
return preg_replace('/&([a-z])[a-z]+;/i', '$1', htmlentities($key));
250256
}
257+
258+
/**
259+
* Resolve the rate limiter name
260+
*
261+
* @param \BackedEnum|\UnitEnum|string $name
262+
* @return string
263+
*/
264+
private function resolveLimiterName($name): string
265+
{
266+
return match (true) {
267+
$name instanceof BackedEnum => $name->value,
268+
$name instanceof UnitEnum => $name->name,
269+
default => (string) $name,
270+
};
271+
}
251272
}

src/Illuminate/Queue/Middleware/RateLimited.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace Illuminate\Queue\Middleware;
44

5+
use BackedEnum;
56
use Illuminate\Cache\RateLimiter;
67
use Illuminate\Cache\RateLimiting\Unlimited;
78
use Illuminate\Container\Container;
89
use Illuminate\Support\Arr;
10+
use UnitEnum;
911

1012
class RateLimited
1113
{
@@ -33,14 +35,18 @@ class RateLimited
3335
/**
3436
* Create a new middleware instance.
3537
*
36-
* @param string $limiterName
38+
* @param \BackedEnum|\UnitEnum|string $limiterName
3739
* @return void
3840
*/
3941
public function __construct($limiterName)
4042
{
4143
$this->limiter = Container::getInstance()->make(RateLimiter::class);
4244

43-
$this->limiterName = $limiterName;
45+
$this->limiterName = match (true) {
46+
$limiterName instanceof BackedEnum => $limiterName->value,
47+
$limiterName instanceof UnitEnum => $limiterName->name,
48+
default => (string) $limiterName,
49+
};
4450
}
4551

4652
/**

tests/Cache/RateLimiterTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Cache;
4+
5+
use Illuminate\Cache\RateLimiter;
6+
use Illuminate\Cache\RateLimiting\Limit;
7+
use Illuminate\Contracts\Cache\Repository as Cache;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use ReflectionProperty;
11+
12+
class RateLimiterTest extends TestCase
13+
{
14+
public static function registerNamedRateLimiterDataProvider(): array
15+
{
16+
return [
17+
'uses BackedEnum' => [BackedEnumNamedRateLimiter::API, 'api'],
18+
'uses UnitEnum' => [UnitEnumNamedRateLimiter::THIRD_PARTY, 'THIRD_PARTY'],
19+
'uses normal string' => ['yolo', 'yolo'],
20+
'uses int' => [100, '100'],
21+
];
22+
}
23+
24+
#[DataProvider('registerNamedRateLimiterDataProvider')]
25+
public function testRegisterNamedRateLimiter(mixed $name, string $expected): void
26+
{
27+
$reflectedLimitersProperty = new ReflectionProperty(RateLimiter::class, 'limiters');
28+
$reflectedLimitersProperty->setAccessible(true);
29+
30+
$rateLimiter = new RateLimiter($this->createMock(Cache::class));
31+
$rateLimiter->for($name, fn () => Limit::perMinute(100));
32+
33+
$limiters = $reflectedLimitersProperty->getValue($rateLimiter);
34+
35+
$this->assertArrayHasKey($expected, $limiters);
36+
37+
$limiterClosure = $rateLimiter->limiter($name);
38+
39+
$this->assertNotNull($limiterClosure);
40+
}
41+
}
42+
43+
enum BackedEnumNamedRateLimiter: string
44+
{
45+
case API = 'api';
46+
}
47+
48+
enum UnitEnumNamedRateLimiter
49+
{
50+
case THIRD_PARTY;
51+
}

tests/Integration/Queue/RateLimitedTest.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@ public function testUnlimitedJobsAreExecuted()
3232
$this->assertJobRanSuccessfully(RateLimitedTestJob::class);
3333
}
3434

35+
public function testUnlimitedJobsAreExecutedUsingBackedEnum()
36+
{
37+
$rateLimiter = $this->app->make(RateLimiter::class);
38+
39+
$rateLimiter->for(BackedEnumNamedRateLimited::FOO, function ($job) {
40+
return Limit::none();
41+
});
42+
43+
$this->assertJobRanSuccessfully(RateLimitedTestJobUsingBackedEnum::class);
44+
$this->assertJobRanSuccessfully(RateLimitedTestJobUsingBackedEnum::class);
45+
}
46+
47+
public function testUnlimitedJobsAreExecutedUsingUnitEnum()
48+
{
49+
$rateLimiter = $this->app->make(RateLimiter::class);
50+
51+
$rateLimiter->for(UnitEnumNamedRateLimited::LARAVEL, function ($job) {
52+
return Limit::none();
53+
});
54+
55+
$this->assertJobRanSuccessfully(RateLimitedTestJobUsingUnitEnum::class);
56+
$this->assertJobRanSuccessfully(RateLimitedTestJobUsingUnitEnum::class);
57+
}
58+
3559
public function testRateLimitedJobsAreNotExecutedOnLimitReached2()
3660
{
3761
$cache = m::mock(Cache::class);
@@ -315,3 +339,47 @@ public function middleware()
315339
return [(new RateLimited('test'))->dontRelease()];
316340
}
317341
}
342+
343+
enum BackedEnumNamedRateLimited: string
344+
{
345+
case FOO = 'bar';
346+
}
347+
348+
enum UnitEnumNamedRateLimited
349+
{
350+
case LARAVEL;
351+
}
352+
353+
class RateLimitedTestJobUsingBackedEnum
354+
{
355+
use InteractsWithQueue, Queueable;
356+
357+
public static $handled = false;
358+
359+
public function handle()
360+
{
361+
static::$handled = true;
362+
}
363+
364+
public function middleware()
365+
{
366+
return [new RateLimited(BackedEnumNamedRateLimited::FOO)];
367+
}
368+
}
369+
370+
class RateLimitedTestJobUsingUnitEnum
371+
{
372+
use InteractsWithQueue, Queueable;
373+
374+
public static $handled = false;
375+
376+
public function handle()
377+
{
378+
static::$handled = true;
379+
}
380+
381+
public function middleware()
382+
{
383+
return [new RateLimited(UnitEnumNamedRateLimited::LARAVEL)];
384+
}
385+
}

0 commit comments

Comments
 (0)