Skip to content

Commit 13d6249

Browse files
committed
Rate limit initialized from history
1 parent 58aa938 commit 13d6249

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

src/Domain/Messaging/Repository/UserMessageRepository.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
namespace PhpList\Core\Domain\Messaging\Repository;
66

7+
use DateTimeInterface;
78
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
89
use PhpList\Core\Domain\Messaging\Model\Message;
10+
use PhpList\Core\Domain\Messaging\Model\Message\UserMessageStatus;
911
use PhpList\Core\Domain\Messaging\Model\UserMessage;
1012
use PhpList\Core\Domain\Subscription\Model\Subscriber;
1113

@@ -15,4 +17,19 @@ public function findOneByUserAndMessage(Subscriber $subscriber, Message $campaig
1517
{
1618
return $this->findOneBy(['user' => $subscriber, 'message' => $campaign]);
1719
}
20+
21+
/**
22+
* Counts how many user messages have status "sent" since the given time.
23+
*/
24+
public function countSentSince(DateTimeInterface $since): int
25+
{
26+
$queryBuilder = $this->createQueryBuilder('um');
27+
$queryBuilder->select('COUNT(um)')
28+
->where('um.createdAt > :since')
29+
->andWhere('um.status = :status')
30+
->setParameter('since', $since)
31+
->setParameter('status', UserMessageStatus::Sent->value);
32+
33+
return (int) $queryBuilder->getQuery()->getSingleScalarResult();
34+
}
1835
}

src/Domain/Messaging/Service/SendRateLimiter.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
namespace PhpList\Core\Domain\Messaging\Service;
66

7+
use DateInterval;
8+
use DateTimeImmutable;
79
use PhpList\Core\Domain\Common\IspRestrictionsProvider;
10+
use PhpList\Core\Domain\Messaging\Repository\UserMessageRepository;
811
use Symfony\Component\Console\Output\OutputInterface;
912

1013
/**
@@ -18,9 +21,11 @@ class SendRateLimiter
1821
private int $throttleSec;
1922
private int $sentInBatch = 0;
2023
private float $batchStart = 0.0;
24+
private bool $initializedFromHistory = false;
2125

2226
public function __construct(
2327
private readonly IspRestrictionsProvider $ispRestrictionsProvider,
28+
private readonly UserMessageRepository $userMessageRepository,
2429
private readonly ?int $mailqueueBatchSize = null,
2530
private readonly ?int $mailqueueBatchPeriod = null,
2631
private readonly ?int $mailqueueThrottle = null,
@@ -51,6 +56,7 @@ private function initializeLimits(): void
5156

5257
$this->sentInBatch = 0;
5358
$this->batchStart = microtime(true);
59+
$this->initializedFromHistory = false;
5460
}
5561

5662
/**
@@ -59,6 +65,13 @@ private function initializeLimits(): void
5965
*/
6066
public function awaitTurn(?OutputInterface $output = null): bool
6167
{
68+
if (!$this->initializedFromHistory && $this->batchSize > 0 && $this->batchPeriod > 0) {
69+
$since = (new DateTimeImmutable())->sub(new DateInterval('PT' . $this->batchPeriod . 'S'));
70+
$alreadySent = $this->userMessageRepository->countSentSince($since);
71+
$this->sentInBatch = max($this->sentInBatch, $alreadySent);
72+
$this->initializedFromHistory = true;
73+
}
74+
6275
if ($this->batchSize > 0 && $this->batchPeriod > 0 && $this->sentInBatch >= $this->batchSize) {
6376
$elapsed = microtime(true) - $this->batchStart;
6477
$remaining = (int)ceil($this->batchPeriod - $elapsed);
@@ -71,7 +84,9 @@ public function awaitTurn(?OutputInterface $output = null): bool
7184
}
7285
$this->batchStart = microtime(true);
7386
$this->sentInBatch = 0;
87+
$this->initializedFromHistory = false;
7488
}
89+
7590
return true;
7691
}
7792

tests/Unit/Domain/Messaging/Service/SendRateLimiterTest.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpList\Core\Domain\Common\IspRestrictionsProvider;
88
use PhpList\Core\Domain\Common\Model\IspRestrictions;
9+
use PhpList\Core\Domain\Messaging\Repository\UserMessageRepository;
910
use PhpList\Core\Domain\Messaging\Service\SendRateLimiter;
1011
use PHPUnit\Framework\MockObject\MockObject;
1112
use PHPUnit\Framework\TestCase;
@@ -25,6 +26,7 @@ public function testInitializesLimitsFromConfigOnly(): void
2526
$this->ispProvider->method('load')->willReturn(new IspRestrictions(null, null, null));
2627
$limiter = new SendRateLimiter(
2728
ispRestrictionsProvider: $this->ispProvider,
29+
userMessageRepository: $this->createMock(UserMessageRepository::class),
2830
mailqueueBatchSize: 5,
2931
mailqueueBatchPeriod: 10,
3032
mailqueueThrottle: 2
@@ -40,7 +42,8 @@ public function testBatchLimitTriggersWaitMessageAndResetsCounters(): void
4042
{
4143
$this->ispProvider->method('load')->willReturn(new IspRestrictions(2, 1, null));
4244
$limiter = new SendRateLimiter(
43-
$this->ispProvider,
45+
ispRestrictionsProvider: $this->ispProvider,
46+
userMessageRepository: $this->createMock(UserMessageRepository::class),
4447
mailqueueBatchSize: 10,
4548
mailqueueBatchPeriod: 1,
4649
mailqueueThrottle: 0
@@ -66,7 +69,8 @@ public function testThrottleSleepsPerMessagePathIsCallable(): void
6669
{
6770
$this->ispProvider->method('load')->willReturn(new IspRestrictions(null, null, null));
6871
$limiter = new SendRateLimiter(
69-
$this->ispProvider,
72+
ispRestrictionsProvider: $this->ispProvider,
73+
userMessageRepository: $this->createMock(UserMessageRepository::class),
7074
mailqueueBatchSize: 0,
7175
mailqueueBatchPeriod: 0,
7276
mailqueueThrottle: 1

0 commit comments

Comments
 (0)