Skip to content

Commit 38ca099

Browse files
committed
ConsecutiveBounceHandler
1 parent 741e6e6 commit 38ca099

File tree

3 files changed

+87
-73
lines changed

3 files changed

+87
-73
lines changed

config/services/services.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@ services:
4343
PhpList\Core\Domain\Common\SystemInfoCollector:
4444
autowire: true
4545
autoconfigure: true
46+
47+
PhpList\Core\Domain\Messaging\Service\ConsecutiveBounceHandler: ~

src/Domain/Messaging/Command/ProcessBouncesCommand.php

Lines changed: 5 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@
55
namespace PhpList\Core\Domain\Messaging\Command;
66

77
use Exception;
8-
use PhpList\Core\Domain\Messaging\Model\Bounce;
9-
use PhpList\Core\Domain\Messaging\Model\UserMessage;
10-
use PhpList\Core\Domain\Messaging\Model\UserMessageBounce;
8+
use PhpList\Core\Domain\Messaging\Service\ConsecutiveBounceHandler;
119
use PhpList\Core\Domain\Messaging\Service\Processor\BounceProtocolProcessor;
1210
use PhpList\Core\Domain\Messaging\Service\Processor\AdvancedBounceRulesProcessor;
1311
use PhpList\Core\Domain\Messaging\Service\Processor\UnidentifiedBounceReprocessor;
1412
use PhpList\Core\Domain\Messaging\Service\LockService;
15-
use PhpList\Core\Domain\Messaging\Service\Manager\BounceManager;
16-
use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository;
17-
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberHistoryManager;
18-
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberManager;
1913
use Psr\Log\LoggerInterface;
2014
use Symfony\Component\Console\Attribute\AsCommand;
2115
use Symfony\Component\Console\Command\Command;
@@ -47,16 +41,13 @@ protected function configure(): void
4741
}
4842

4943
public function __construct(
50-
private readonly BounceManager $bounceManager,
5144
private readonly LockService $lockService,
5245
private readonly LoggerInterface $logger,
53-
private readonly SubscriberManager $subscriberManager,
54-
private readonly SubscriberHistoryManager $subscriberHistoryManager,
55-
private readonly SubscriberRepository $subscriberRepository,
5646
/** @var iterable<BounceProtocolProcessor> */
5747
private readonly iterable $protocolProcessors,
5848
private readonly AdvancedBounceRulesProcessor $advancedRulesProcessor,
5949
private readonly UnidentifiedBounceReprocessor $unidentifiedBounceReprocessor,
50+
private readonly ConsecutiveBounceHandler $consecutiveBounceHandler,
6051
) {
6152
parent::__construct();
6253
}
@@ -83,6 +74,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8374
try {
8475
$io->title('Processing bounces');
8576
$protocol = (string)$input->getOption('protocol');
77+
$unsubscribeThreshold = (int)$input->getOption('unsubscribe-threshold');
78+
$blacklistThreshold = (int)$input->getOption('blacklist-threshold');
8679

8780
$downloadReport = '';
8881

@@ -103,15 +96,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
10396
$downloadReport .= $processor->process($input, $io);
10497
$this->unidentifiedBounceReprocessor->process($io);
10598
$this->advancedRulesProcessor->process($io, (int)$input->getOption('rules-batch-size'));
106-
107-
$this->handleConsecutiveBounces(
108-
$io,
109-
(int)$input->getOption('unsubscribe-threshold'),
110-
(int)$input->getOption('blacklist-threshold')
111-
);
99+
$this->consecutiveBounceHandler->handle($io, $unsubscribeThreshold, $blacklistThreshold);
112100

113101
$this->logger->info('Bounce processing completed', ['downloadReport' => $downloadReport]);
114-
115102
$io->success('Bounce processing completed.');
116103

117104
return Command::SUCCESS;
@@ -124,59 +111,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int
124111
$this->lockService->release($lock);
125112
}
126113
}
127-
128-
private function handleConsecutiveBounces(SymfonyStyle $io, int $unsubscribeThreshold, int $blacklistThreshold): void
129-
{
130-
$io->section('Identifying consecutive bounces');
131-
$users = $this->subscriberRepository->distinctUsersWithBouncesConfirmedNotBlacklisted();
132-
$total = count($users);
133-
if ($total === 0) {
134-
$io->writeln('Nothing to do');
135-
return;
136-
}
137-
$usercnt = 0;
138-
foreach ($users as $user) {
139-
$usercnt++;
140-
$history = $this->bounceManager->getUserMessageHistoryWithBounces($user);
141-
$cnt = 0; $removed = false; $msgokay = false; $unsubscribed = false;
142-
foreach ($history as $bounce) {
143-
/** @var $bounce array{um: UserMessage, umb: UserMessageBounce|null, b: Bounce|null} */
144-
if (
145-
stripos($bounce['b']->getStatus() ?? '', 'duplicate') === false
146-
&& stripos($bounce['b']->getComment() ?? '', 'duplicate') === false
147-
) {
148-
if ($bounce['b']->getId()) {
149-
$cnt++;
150-
if ($cnt >= $unsubscribeThreshold) {
151-
if (!$unsubscribed) {
152-
$this->subscriberManager->markUnconfirmed($user->getId());
153-
$this->subscriberHistoryManager->addHistory(
154-
subscriber: $user,
155-
message: 'Auto Unconfirmed',
156-
details: sprintf('Subscriber auto unconfirmed for %d consecutive bounces', $cnt)
157-
);
158-
$unsubscribed = true;
159-
}
160-
if ($blacklistThreshold > 0 && $cnt >= $blacklistThreshold) {
161-
$this->subscriberManager->blacklist(
162-
subscriber: $user,
163-
reason: sprintf('%d consecutive bounces, threshold reached', $cnt)
164-
);
165-
$removed = true;
166-
}
167-
}
168-
} else {
169-
break;
170-
}
171-
}
172-
if ($removed) {
173-
break;
174-
}
175-
}
176-
if ($usercnt % 5 === 0) {
177-
$io->writeln(sprintf('processed %d out of %d subscribers', $usercnt, $total));
178-
}
179-
}
180-
$io->writeln(sprintf('total of %d subscribers processed', $total));
181-
}
182114
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Service;
6+
7+
use PhpList\Core\Domain\Messaging\Model\Bounce;
8+
use PhpList\Core\Domain\Messaging\Model\UserMessage;
9+
use PhpList\Core\Domain\Messaging\Model\UserMessageBounce;
10+
use PhpList\Core\Domain\Messaging\Service\Manager\BounceManager;
11+
use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository;
12+
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberHistoryManager;
13+
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberManager;
14+
use Symfony\Component\Console\Style\SymfonyStyle;
15+
16+
class ConsecutiveBounceHandler
17+
{
18+
public function __construct(
19+
private readonly BounceManager $bounceManager,
20+
private readonly SubscriberRepository $subscriberRepository,
21+
private readonly SubscriberManager $subscriberManager,
22+
private readonly SubscriberHistoryManager $subscriberHistoryManager,
23+
) {
24+
}
25+
26+
public function handle(SymfonyStyle $io, int $unsubscribeThreshold, int $blacklistThreshold): void
27+
{
28+
$io->section('Identifying consecutive bounces');
29+
$users = $this->subscriberRepository->distinctUsersWithBouncesConfirmedNotBlacklisted();
30+
$total = count($users);
31+
if ($total === 0) {
32+
$io->writeln('Nothing to do');
33+
return;
34+
}
35+
$usercnt = 0;
36+
foreach ($users as $user) {
37+
$usercnt++;
38+
$history = $this->bounceManager->getUserMessageHistoryWithBounces($user);
39+
$cnt = 0; $removed = false; $unsubscribed = false;
40+
foreach ($history as $bounce) {
41+
/** @var $bounce array{um: UserMessage, umb: UserMessageBounce|null, b: Bounce|null} */
42+
if (
43+
stripos($bounce['b']->getStatus() ?? '', 'duplicate') === false
44+
&& stripos($bounce['b']->getComment() ?? '', 'duplicate') === false
45+
) {
46+
if ($bounce['b']->getId()) {
47+
$cnt++;
48+
if ($cnt >= $unsubscribeThreshold) {
49+
if (!$unsubscribed) {
50+
$this->subscriberManager->markUnconfirmed($user->getId());
51+
$this->subscriberHistoryManager->addHistory(
52+
subscriber: $user,
53+
message: 'Auto Unconfirmed',
54+
details: sprintf('Subscriber auto unconfirmed for %d consecutive bounces', $cnt)
55+
);
56+
$unsubscribed = true;
57+
}
58+
if ($blacklistThreshold > 0 && $cnt >= $blacklistThreshold) {
59+
$this->subscriberManager->blacklist(
60+
subscriber: $user,
61+
reason: sprintf('%d consecutive bounces, threshold reached', $cnt)
62+
);
63+
$removed = true;
64+
}
65+
}
66+
} else {
67+
break;
68+
}
69+
}
70+
if ($removed) {
71+
break;
72+
}
73+
}
74+
if ($usercnt % 5 === 0) {
75+
$io->writeln(sprintf('processed %d out of %d subscribers', $usercnt, $total));
76+
}
77+
}
78+
$io->writeln(sprintf('total of %d subscribers processed', $total));
79+
}
80+
}

0 commit comments

Comments
 (0)