Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] Allows Unit & Backed Enums for registering named RateLimiter & RateLimited middleware #52935

Merged
merged 3 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)];
}
}