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

Fix #44: Use PSR-20 ClockInterface instead of TimerInterface #47

Closed
wants to merge 7 commits into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 3.0.0 under development

- Chg #44: Use PSR-20 `ClockInterface` instead of `TimerInterface` (@samdark)
- New #43: Add APCu counters storage (@jiaweipan)
- Enh #41: Adapt package to concurrent requests, for this `StorageInterface` method `save()` split to
`saveIfNotExists()` and `saveCompareAndSwap()` (@jiaweipan)
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
[![static analysis](https://github.com/yiisoft/rate-limiter/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/rate-limiter/actions?query=workflow%3A%22static+analysis%22)
[![type-coverage](https://shepherd.dev/github/yiisoft/rate-limiter/coverage.svg)](https://shepherd.dev/github/yiisoft/rate-limiter)

Rate limiter middleware helps to prevent abuse by limiting the number of requests that could be me made consequentially.
Rate limiter middleware helps to prevent abuse by limiting the number of requests that could be made consequentially.

For example, you may want to limit the API usage of each user to be at most 100 API calls within a period of 10 minutes.
If too many requests are received from a user within the stated period of the time, a response with status code 429
Expand Down Expand Up @@ -61,9 +61,9 @@ are limited and 5 is a period to apply limit to, in seconds.
The `Counter` implements [generic cell rate limit algorithm (GCRA)](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm)
that ensures that after reaching the limit further increments are distributed equally.

> Note: While it is sufficiently effective, it is preferred to use [Nginx](https://www.nginx.com/blog/rate-limiting-nginx/)
> Note: While it's sufficiently effective, it's preferred to use [Nginx](https://www.nginx.com/blog/rate-limiting-nginx/)
> or another webserver capabilities for rate limiting. This package allows rate-limiting in the project with deployment
> environment you cannot control such as installable CMS.
> environment you can't control such as installable CMS.

### Implementing your own limiting policy

Expand All @@ -84,7 +84,7 @@ Easiest way to customize a policy is to use `LimitCallback`:

```php
$middleware = new LimitRequestsMiddleware($counter, $responseFactory, new LimitCallback(function (ServerRequestInterface $request): string {
// return user id from database if authentication id used i.e. limit guests and each authenticated user separately.
// return user id from a database if authentication id used i.e. limit guests and each authenticated user separately.
}));
```

Expand Down Expand Up @@ -127,7 +127,7 @@ The code is statically analyzed with [Psalm](https://psalm.dev/). To run static

## License

The Yii Rate Limiter Middleware is free software. It is released under the terms of the BSD License.
The Yii Rate Limiter Middleware is free software. It's released under the terms of the BSD License.
Please see [`LICENSE`](./LICENSE.md) for more information.

Maintained by [Yii Software](https://www.yiiframework.com/).
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/simple-cache": "^2.0|^3.0",
"yiisoft/http": "^1.2"
"yiisoft/http": "^1.2",
"psr/clock": "^1.0"
},
"require-dev": {
"ext-apcu": "*",
Expand Down
22 changes: 11 additions & 11 deletions src/Counter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace Yiisoft\Yii\RateLimiter;

use InvalidArgumentException;
use Psr\Clock\ClockInterface;
use Yiisoft\Yii\RateLimiter\Storage\StorageInterface;
use Yiisoft\Yii\RateLimiter\Time\MicrotimeTimer;
use Yiisoft\Yii\RateLimiter\Time\TimerInterface;
use Yiisoft\Yii\RateLimiter\Time\SystemClock;

/**
* Counter implements generic cell rate limit algorithm (GCRA) that ensures that after reaching the limit further
Expand All @@ -28,19 +28,19 @@
private int $periodInMilliseconds;

/**
* @var float Maximum interval before next increment.
* In GCRA it is known as emission interval.
* @var float Maximum interval before the next increment.
* In GCRA it's known as an emission interval.
*/
private float $incrementIntervalInMilliseconds;
private TimerInterface $timer;
private ClockInterface $timer;

/**
* @param StorageInterface $storage Storage to use for counter values.
* @param int $limit Maximum number of increments that could be performed before increments are limited.
* @param int $limit A maximum number of increments that could be performed before increments are limited.
* @param int $periodInSeconds Period to apply limit to.
* @param int $storageTtlInSeconds Storage TTL. Should be higher than `$periodInSeconds`.
* @param string $storagePrefix Storage prefix.
* @param TimerInterface|null $timer Timer instance to get current time from.
* @param ClockInterface|null $timer Timer instance to get current time from.
* @param int $maxCasAttempts Maximum number of times to retry saveIfNotExists/saveCompareAndSwap operations before returning an error.
*/
public function __construct(
Expand All @@ -49,7 +49,7 @@
int $periodInSeconds,
private int $storageTtlInSeconds = self::DEFAULT_TTL,
private string $storagePrefix = self::ID_PREFIX,
TimerInterface|null $timer = null,
ClockInterface|null $timer = null,
private int $maxCasAttempts = self::DEFAULT_MAX_CAS_ATTEMPTS,
) {
if ($limit < 1) {
Expand All @@ -61,7 +61,7 @@
}

$this->periodInMilliseconds = $periodInSeconds * self::MILLISECONDS_PER_SECOND;
$this->timer = $timer ?: new MicrotimeTimer();
$this->timer = $timer ?: new SystemClock();
$this->incrementIntervalInMilliseconds = $this->periodInMilliseconds / $this->limit;
}

Expand All @@ -71,11 +71,11 @@
public function hit(string $id): CounterState
{
$attempts = 0;
$isFailStoreUpdatedData = false;

Check warning on line 74 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "FalseValue": --- Original +++ New @@ @@ public function hit(string $id) : CounterState { $attempts = 0; - $isFailStoreUpdatedData = false; + $isFailStoreUpdatedData = true; do { // Last increment time. // In GCRA it's known as arrival time.

Check warning on line 74 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "FalseValue": --- Original +++ New @@ @@ public function hit(string $id) : CounterState { $attempts = 0; - $isFailStoreUpdatedData = false; + $isFailStoreUpdatedData = true; do { // Last increment time. // In GCRA it's known as arrival time.
do {
// Last increment time.
// In GCRA it's known as arrival time.
$lastIncrementTimeInMilliseconds = $this->timer->nowInMilliseconds();
$lastIncrementTimeInMilliseconds = round((float)$this->timer->now()->format('U.u') * 1000);

Check warning on line 78 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ do { // Last increment time. // In GCRA it's known as arrival time. - $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1000); + $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1001); $lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id); $theoreticalNextIncrementTime = $this->calculateTheoreticalNextIncrementTime($lastIncrementTimeInMilliseconds, $lastStoredTheoreticalNextIncrementTime); $remaining = $this->calculateRemaining($lastIncrementTimeInMilliseconds, $theoreticalNextIncrementTime);

Check warning on line 78 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ do { // Last increment time. // In GCRA it's known as arrival time. - $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1000); + $lastIncrementTimeInMilliseconds = floor((float) $this->timer->now()->format('U.u') * 1000); $lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id); $theoreticalNextIncrementTime = $this->calculateTheoreticalNextIncrementTime($lastIncrementTimeInMilliseconds, $lastStoredTheoreticalNextIncrementTime); $remaining = $this->calculateRemaining($lastIncrementTimeInMilliseconds, $theoreticalNextIncrementTime);

Check warning on line 78 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ do { // Last increment time. // In GCRA it's known as arrival time. - $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1000); + $lastIncrementTimeInMilliseconds = ceil((float) $this->timer->now()->format('U.u') * 1000); $lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id); $theoreticalNextIncrementTime = $this->calculateTheoreticalNextIncrementTime($lastIncrementTimeInMilliseconds, $lastStoredTheoreticalNextIncrementTime); $remaining = $this->calculateRemaining($lastIncrementTimeInMilliseconds, $theoreticalNextIncrementTime);

Check warning on line 78 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ do { // Last increment time. // In GCRA it's known as arrival time. - $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1000); + $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1001); $lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id); $theoreticalNextIncrementTime = $this->calculateTheoreticalNextIncrementTime($lastIncrementTimeInMilliseconds, $lastStoredTheoreticalNextIncrementTime); $remaining = $this->calculateRemaining($lastIncrementTimeInMilliseconds, $theoreticalNextIncrementTime);

Check warning on line 78 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ do { // Last increment time. // In GCRA it's known as arrival time. - $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1000); + $lastIncrementTimeInMilliseconds = floor((float) $this->timer->now()->format('U.u') * 1000); $lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id); $theoreticalNextIncrementTime = $this->calculateTheoreticalNextIncrementTime($lastIncrementTimeInMilliseconds, $lastStoredTheoreticalNextIncrementTime); $remaining = $this->calculateRemaining($lastIncrementTimeInMilliseconds, $theoreticalNextIncrementTime);

Check warning on line 78 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ do { // Last increment time. // In GCRA it's known as arrival time. - $lastIncrementTimeInMilliseconds = round((float) $this->timer->now()->format('U.u') * 1000); + $lastIncrementTimeInMilliseconds = ceil((float) $this->timer->now()->format('U.u') * 1000); $lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id); $theoreticalNextIncrementTime = $this->calculateTheoreticalNextIncrementTime($lastIncrementTimeInMilliseconds, $lastStoredTheoreticalNextIncrementTime); $remaining = $this->calculateRemaining($lastIncrementTimeInMilliseconds, $theoreticalNextIncrementTime);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$lastIncrementTimeInMilliseconds = round((float)$this->timer->now()->format('U.u') * 1000);
$lastIncrementTimeInMilliseconds = round($this->timer->now()->format('U.u') * 1000);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make static analysis complain.


$lastStoredTheoreticalNextIncrementTime = $this->getLastStoredTheoreticalNextIncrementTime($id);

Expand Down Expand Up @@ -103,7 +103,7 @@
$attempts++;
if ($attempts >= $this->maxCasAttempts) {
$isFailStoreUpdatedData = true;
break;

Check warning on line 106 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ $attempts++; if ($attempts >= $this->maxCasAttempts) { $isFailStoreUpdatedData = true; - break; + continue; } } while (true); return new CounterState($this->limit, $remaining, $resetAfter, $isFailStoreUpdatedData);

Check warning on line 106 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ $attempts++; if ($attempts >= $this->maxCasAttempts) { $isFailStoreUpdatedData = true; - break; + continue; } } while (true); return new CounterState($this->limit, $remaining, $resetAfter, $isFailStoreUpdatedData);
}
} while (true);

Expand All @@ -112,7 +112,7 @@

/**
* @return float Theoretical increment time that would be expected from equally spaced increments at exactly rate
* limit. In GCRA it is known as TAT, theoretical arrival time.
* limit. In GCRA it's known as TAT, theoretical arrival time.
*/
private function calculateTheoreticalNextIncrementTime(
float $lastIncrementTimeInMilliseconds,
Expand All @@ -134,8 +134,8 @@
): int {
$incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds;

$remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt);

Check warning on line 137 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ private function calculateRemaining(float $lastIncrementTimeInMilliseconds, float $theoreticalNextIncrementTime) : int { $incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds; - $remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt); + $remainingTimeInMilliseconds = floor($lastIncrementTimeInMilliseconds - $incrementAllowedAt); if ($remainingTimeInMilliseconds > 0) { return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds); }

Check warning on line 137 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ private function calculateRemaining(float $lastIncrementTimeInMilliseconds, float $theoreticalNextIncrementTime) : int { $incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds; - $remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt); + $remainingTimeInMilliseconds = ceil($lastIncrementTimeInMilliseconds - $incrementAllowedAt); if ($remainingTimeInMilliseconds > 0) { return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds); }

Check warning on line 137 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ private function calculateRemaining(float $lastIncrementTimeInMilliseconds, float $theoreticalNextIncrementTime) : int { $incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds; - $remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt); + $remainingTimeInMilliseconds = floor($lastIncrementTimeInMilliseconds - $incrementAllowedAt); if ($remainingTimeInMilliseconds > 0) { return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds); }

Check warning on line 137 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "RoundingFamily": --- Original +++ New @@ @@ private function calculateRemaining(float $lastIncrementTimeInMilliseconds, float $theoreticalNextIncrementTime) : int { $incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds; - $remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt); + $remainingTimeInMilliseconds = ceil($lastIncrementTimeInMilliseconds - $incrementAllowedAt); if ($remainingTimeInMilliseconds > 0) { return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds); }
if ($remainingTimeInMilliseconds > 0) {

Check warning on line 138 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "GreaterThan": --- Original +++ New @@ @@ { $incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds; $remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt); - if ($remainingTimeInMilliseconds > 0) { + if ($remainingTimeInMilliseconds >= 0) { return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds); } return 0;

Check warning on line 138 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "GreaterThan": --- Original +++ New @@ @@ { $incrementAllowedAt = $theoreticalNextIncrementTime - $this->periodInMilliseconds; $remainingTimeInMilliseconds = round($lastIncrementTimeInMilliseconds - $incrementAllowedAt); - if ($remainingTimeInMilliseconds > 0) { + if ($remainingTimeInMilliseconds >= 0) { return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds); } return 0;
return (int) ($remainingTimeInMilliseconds / $this->incrementIntervalInMilliseconds);
}

Expand Down Expand Up @@ -173,7 +173,7 @@
*/
private function calculateResetAfter(float $theoreticalNextIncrementTime): int
{
return (int) ($theoreticalNextIncrementTime / self::MILLISECONDS_PER_SECOND);

Check warning on line 176 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "Division": --- Original +++ New @@ @@ */ private function calculateResetAfter(float $theoreticalNextIncrementTime) : int { - return (int) ($theoreticalNextIncrementTime / self::MILLISECONDS_PER_SECOND); + return (int) ($theoreticalNextIncrementTime * self::MILLISECONDS_PER_SECOND); } /** * @return string Storage key used to store the next increment time.

Check warning on line 176 in src/Counter.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "Division": --- Original +++ New @@ @@ */ private function calculateResetAfter(float $theoreticalNextIncrementTime) : int { - return (int) ($theoreticalNextIncrementTime / self::MILLISECONDS_PER_SECOND); + return (int) ($theoreticalNextIncrementTime * self::MILLISECONDS_PER_SECOND); } /** * @return string Storage key used to store the next increment time.
}

/**
Expand Down
15 changes: 0 additions & 15 deletions src/Time/MicrotimeTimer.php

This file was deleted.

16 changes: 16 additions & 0 deletions src/Time/SystemClock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\RateLimiter\Time;

use DateTimeImmutable;
use Psr\Clock\ClockInterface;

final class SystemClock implements ClockInterface
{
public function now(): DateTimeImmutable
{
return new DateTimeImmutable();
}
}
10 changes: 0 additions & 10 deletions src/Time/TimerInterface.php

This file was deleted.

6 changes: 3 additions & 3 deletions tests/ApcuCounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Yiisoft\Yii\RateLimiter\Storage\ApcuStorage;
use Yiisoft\Yii\RateLimiter\Storage\StorageInterface;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FakeApcuStorage;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenTimeTimer;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenClock;

final class ApcuCounterTest extends BaseCounterTest
{
Expand All @@ -35,7 +35,7 @@ protected function setUp(): void
*/
public function testConcurrentHitsWithDirtyReading(): void
{
$timer = new FrozenTimeTimer();
$timer = new FrozenClock();
$storage = new FakeApcuStorage(5);
$limitHits = 10;
$counter = new Counter(
Expand Down Expand Up @@ -64,7 +64,7 @@ public function testConcurrentHitsWithDirtyReading(): void

public function testIsExceedingMaxAttempts(): void
{
$timer = new FrozenTimeTimer();
$timer = new FrozenClock();
$dirtyReadCount = 2;
$storage = new FakeApcuStorage($dirtyReadCount);
$counter = new Counter(
Expand Down
13 changes: 7 additions & 6 deletions tests/BaseCounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
use PHPUnit\Framework\TestCase;
use Yiisoft\Yii\RateLimiter\Counter;
use Yiisoft\Yii\RateLimiter\Storage\StorageInterface;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenTimeTimer;
use Yiisoft\Yii\RateLimiter\Time\MicrotimeTimer;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenClock;
use Yiisoft\Yii\RateLimiter\Tests\Support\Assert;

abstract class BaseCounterTest extends TestCase
Expand Down Expand Up @@ -54,7 +53,8 @@ public function testShouldNotBeAbleToSetInvalidPeriod(): void

public function testIncrementMustBeUniformAfterLimitIsReached(): void
{
$timer = new FrozenTimeTimer();
$timer = new FrozenClock();

$counter = new Counter(
$this->getStorage(),
10,
Expand All @@ -72,7 +72,7 @@ public function testIncrementMustBeUniformAfterLimitIsReached(): void
for ($i = 0; $i < 5; $i++) {
// Move timer forward for (period in milliseconds / limit)
// i.e. once in period / limit remaining allowance should be increased by 1.
FrozenTimeTimer::setTimeMark($timer->nowInMilliseconds() + 100);
$timer->modify('+100 milliseconds');
$statistics = $counter->hit('key');
$this->assertEquals(1, $statistics->getRemaining());
}
Expand All @@ -82,18 +82,19 @@ public function testCustomTtl(): void
{
$storage = $this->getStorage();

$clock = new FrozenClock();
$counter = new Counter(
$storage,
1,
1,
1,
'rate-limiter-',
new FrozenTimeTimer()
$clock
);

$counter->hit('test');

FrozenTimeTimer::setTimeMark((new MicrotimeTimer())->nowInMilliseconds() + 2);
$clock->modify('+2 milliseconds');

self::assertNull($storage->get('rate-limiter-test'));
}
Expand Down
6 changes: 3 additions & 3 deletions tests/CounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Yiisoft\Yii\RateLimiter\Storage\SimpleCacheStorage;
use Yiisoft\Yii\RateLimiter\Storage\StorageInterface;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FakeSimpleCacheStorage;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenTimeTimer;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenClock;

final class CounterTest extends BaseCounterTest
{
Expand All @@ -20,11 +20,11 @@ protected function getStorage(): StorageInterface

/**
* Testing that in concurrent scenarios, when dirty reads occur,
* the current limiter cannot be as expected By 'SimpleCacheStorage'.
* the current limiter can't be as expected By 'SimpleCacheStorage'.
*/
public function testConcurrentHitsWithDirtyReading(): void
{
$timer = new FrozenTimeTimer();
$timer = new FrozenClock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can don't use specific timer and replace it to null.

$storage = new FakeSimpleCacheStorage(new ArrayCache(), 5);
$limitHits = 10;
$counter = new Counter(
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/FakeApcuStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function get(string $key): ?float
if ($readValue === false) {
return null;
}

$readValue = (float) ($readValue / $this->fixPrecisionRate);
$this->dirtyReadValue = $readValue;
$this->remainingDirtyReadCount = $this->dirtyReadCount;
Expand Down
31 changes: 31 additions & 0 deletions tests/Fixtures/FrozenClock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\RateLimiter\Tests\Fixtures;

use DateTimeImmutable;
use Psr\Clock\ClockInterface;

/**
* Frozen timer returns the same value for all calls.
*/
final class FrozenClock implements ClockInterface
{
private DateTimeImmutable $now;

public function __construct()
{
$this->now = new DateTimeImmutable();
}

public function now(): DateTimeImmutable
{
return $this->now;
}

public function modify(string $modifier): void
{
$this->now = $this->now->modify($modifier);
}
}
37 changes: 0 additions & 37 deletions tests/Fixtures/FrozenTimeTimer.php

This file was deleted.

6 changes: 3 additions & 3 deletions tests/MiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
use Yiisoft\Yii\RateLimiter\Storage\SimpleCacheStorage;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FakeApcuStorage;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FakeCounter;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenTimeTimer;
use Yiisoft\Yii\RateLimiter\Tests\Fixtures\FrozenClock;

final class MiddlewareTest extends TestCase
{
Expand Down Expand Up @@ -227,7 +227,7 @@ public function testWithLimitingFunction(): void
*/
public function testWithExceedingMaxAttempts(): void
{
$timer = new FrozenTimeTimer();
$timer = new FrozenClock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can don't use specific timer and replace it to null.

$dirtyReadCount = 2;
$storage = new FakeApcuStorage($dirtyReadCount);
$counter = new Counter(
Expand Down Expand Up @@ -259,7 +259,7 @@ public function testWithExceedingMaxAttempts(): void

public function testFailStoreUpdatedDataMiddleware(): void
{
$timer = new FrozenTimeTimer();
$timer = new FrozenClock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can don't use specific timer and replace it to null.

$dirtyReadCount = 2;
$storage = new FakeApcuStorage($dirtyReadCount);
$counter = new Counter(
Expand Down
Loading
Loading