Skip to content

Commit 981ca1e

Browse files
committed
ProcessBouncesCommand
1 parent 30413a7 commit 981ca1e

14 files changed

+927
-31
lines changed

src/Domain/Messaging/Command/ProcessBouncesCommand.php

Lines changed: 542 additions & 0 deletions
Large diffs are not rendered by default.

src/Domain/Messaging/Model/BounceRegexBounce.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,38 @@
1313
class BounceRegexBounce implements DomainModel
1414
{
1515
#[ORM\Id]
16-
#[ORM\Column(type: 'integer')]
17-
private int $regex;
16+
#[ORM\Column(name: 'regex', type: 'integer')]
17+
private int $regexId;
1818

1919
#[ORM\Id]
20-
#[ORM\Column(type: 'integer')]
21-
private int $bounce;
20+
#[ORM\Column(name: 'bounce', type: 'integer')]
21+
private int $bounceId;
2222

23-
public function __construct(int $regex, int $bounce)
23+
public function __construct(int $regexId, int $bounceId)
2424
{
25-
$this->regex = $regex;
26-
$this->bounce = $bounce;
25+
$this->regexId = $regexId;
26+
$this->bounceId = $bounceId;
2727
}
2828

29-
public function getRegex(): int
29+
public function getRegexId(): int
3030
{
31-
return $this->regex;
31+
return $this->regexId;
3232
}
3333

34-
public function setRegex(int $regex): self
34+
public function setRegexId(int $regexId): self
3535
{
36-
$this->regex = $regex;
36+
$this->regexId = $regexId;
3737
return $this;
3838
}
3939

40-
public function getBounce(): int
40+
public function getBounceId(): int
4141
{
42-
return $this->bounce;
42+
return $this->bounceId;
4343
}
4444

45-
public function setBounce(int $bounce): self
45+
public function setBounceId(int $bounceId): self
4646
{
47-
$this->bounce = $bounce;
47+
$this->bounceId = $bounceId;
4848
return $this;
4949
}
5050
}

src/Domain/Messaging/Model/UserMessageBounce.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ class UserMessageBounce implements DomainModel, Identity
3131
private int $messageId;
3232

3333
#[ORM\Column(name: 'bounce', type: 'integer')]
34-
private int $bounce;
34+
private int $bounceId;
3535

3636
#[ORM\Column(name: 'time', type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])]
3737
private DateTime $createdAt;
3838

39-
public function __construct(int $bounce)
39+
public function __construct(int $bounceId, DateTime $createdAt)
4040
{
41-
$this->bounce = $bounce;
42-
$this->createdAt = new DateTime();
41+
$this->bounceId = $bounceId;
42+
$this->createdAt = $createdAt;
4343
}
4444

4545
public function getId(): ?int
@@ -57,9 +57,9 @@ public function getMessageId(): int
5757
return $this->messageId;
5858
}
5959

60-
public function getBounce(): int
60+
public function getBounceId(): int
6161
{
62-
return $this->bounce;
62+
return $this->bounceId;
6363
}
6464

6565
public function getCreatedAt(): DateTime
@@ -79,9 +79,9 @@ public function setMessageId(int $messageId): self
7979
return $this;
8080
}
8181

82-
public function setBounce(int $bounce): self
82+
public function setBounceId(int $bounceId): self
8383
{
84-
$this->bounce = $bounce;
84+
$this->bounceId = $bounceId;
8585
return $this;
8686
}
8787
}

src/Domain/Messaging/Repository/BounceRegexRepository.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,16 @@ public function findOneByRegexHash(string $regexHash): ?BounceRegex
1717
{
1818
return $this->findOneBy(['regexHash' => $regexHash]);
1919
}
20+
21+
/** @return BounceRegex[] */
22+
public function fetchAllOrdered(): array
23+
{
24+
return $this->findBy([], ['listOrder' => 'ASC']);
25+
}
26+
27+
/** @return BounceRegex[] */
28+
public function fetchActiveOrdered(): array
29+
{
30+
return $this->findBy(['active' => true], ['listOrder' => 'ASC']);
31+
}
2032
}

src/Domain/Messaging/Repository/BounceRepository.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@
77
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
88
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
99
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
10+
use PhpList\Core\Domain\Messaging\Model\Bounce;
1011

1112
class BounceRepository extends AbstractRepository implements PaginatableRepositoryInterface
1213
{
1314
use CursorPaginationTrait;
15+
16+
/** @return Bounce[] */
17+
public function findByStatus(string $status): array
18+
{
19+
return $this->findBy(['status' => $status]);
20+
}
1421
}

src/Domain/Messaging/Repository/MessageRepository.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,15 @@ public function getMessagesByList(SubscriberList $list): array
6363
->getQuery()
6464
->getResult();
6565
}
66+
67+
public function incrementBounceCount(int $messageId): void
68+
{
69+
$this->createQueryBuilder('m')
70+
->update()
71+
->set('m.bounceCount', 'm.bounceCount + 1')
72+
->where('m.id = :messageId')
73+
->setParameter('messageId', $messageId)
74+
->getQuery()
75+
->execute();
76+
}
6677
}

src/Domain/Messaging/Repository/UserMessageBounceRepository.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
88
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
99
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
10+
use PhpList\Core\Domain\Messaging\Model\Bounce;
11+
use PhpList\Core\Domain\Messaging\Model\UserMessageBounce;
1012

1113
class UserMessageBounceRepository extends AbstractRepository implements PaginatableRepositoryInterface
1214
{
@@ -21,4 +23,35 @@ public function getCountByMessageId(int $messageId): int
2123
->getQuery()
2224
->getSingleScalarResult();
2325
}
26+
27+
public function existsByMessageIdAndUserId(int $messageId, int $subscriberId): bool
28+
{
29+
$qb = $this->createQueryBuilder('umb')
30+
->select('1')
31+
->where('umb.messageId = :messageId')
32+
->andWhere('umb.userId = :userId')
33+
->setParameter('messageId', $messageId)
34+
->setParameter('userId', $subscriberId)
35+
->setMaxResults(1);
36+
37+
return (bool) $qb->getQuery()->getOneOrNullResult();
38+
}
39+
40+
/**
41+
* @return array<int, array{umb: UserMessageBounce, bounce: Bounce}>
42+
*/
43+
public function getPaginatedWithJoinNoRelation(int $fromId, int $limit): array
44+
{
45+
return $this->getEntityManager()
46+
->createQueryBuilder()
47+
->select('umb', 'bounce')
48+
->from(UserMessageBounce::class, 'umb')
49+
->innerJoin(Bounce::class, 'bounce', 'WITH', 'bounce.id = umb.bounce')
50+
->where('umb.id > :id')
51+
->setParameter('id', $fromId)
52+
->orderBy('umb.id', 'ASC')
53+
->setMaxResults($limit)
54+
->getQuery()
55+
->getResult();
56+
}
2457
}

src/Domain/Messaging/Service/Manager/BounceManager.php

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,34 @@
55
namespace PhpList\Core\Domain\Messaging\Service\Manager;
66

77
use DateTime;
8+
use DateTimeImmutable;
89
use PhpList\Core\Domain\Messaging\Model\Bounce;
10+
use PhpList\Core\Domain\Messaging\Model\UserMessageBounce;
911
use PhpList\Core\Domain\Messaging\Repository\BounceRepository;
12+
use PhpList\Core\Domain\Messaging\Repository\UserMessageBounceRepository;
1013

1114
class BounceManager
1215
{
1316
private BounceRepository $bounceRepository;
17+
private UserMessageBounceRepository $userMessageBounceRepository;
1418

15-
public function __construct(BounceRepository $bounceRepository)
16-
{
19+
public function __construct(
20+
BounceRepository $bounceRepository,
21+
UserMessageBounceRepository $userMessageBounceRepository
22+
) {
1723
$this->bounceRepository = $bounceRepository;
24+
$this->userMessageBounceRepository = $userMessageBounceRepository;
1825
}
1926

2027
public function create(
21-
?DateTime $date = null,
28+
?DateTimeImmutable $date = null,
2229
?string $header = null,
2330
?string $data = null,
2431
?string $status = null,
2532
?string $comment = null
2633
): Bounce {
2734
$bounce = new Bounce(
28-
date: $date,
35+
date: DateTime::createFromImmutable($date),
2936
header: $header,
3037
data: $data,
3138
status: $status,
@@ -37,9 +44,13 @@ public function create(
3744
return $bounce;
3845
}
3946

40-
public function save(Bounce $bounce): void
47+
public function update(Bounce $bounce, ?string $status = null, ?string $comment = null): Bounce
4148
{
49+
$bounce->setStatus($status);
50+
$bounce->setComment($comment);
4251
$this->bounceRepository->save($bounce);
52+
53+
return $bounce;
4354
}
4455

4556
public function delete(Bounce $bounce): void
@@ -59,4 +70,40 @@ public function getById(int $id): ?Bounce
5970
$found = $this->bounceRepository->find($id);
6071
return $found;
6172
}
73+
74+
public function linkUserMessageBounce(
75+
Bounce $bounce,
76+
DateTimeImmutable $date,
77+
int $subscriberId,
78+
?int $messageId = -1
79+
): UserMessageBounce {
80+
$userMessageBounce = new UserMessageBounce($bounce->getId(), DateTime::createFromImmutable($date));
81+
$userMessageBounce->setUserId($subscriberId);
82+
$userMessageBounce->setMessageId($messageId);
83+
84+
return $userMessageBounce;
85+
}
86+
87+
public function existsUserMessageBounce(int $subscriberId, int $messageId): bool
88+
{
89+
return $this->userMessageBounceRepository->existsByMessageIdAndUserId($messageId, $subscriberId);
90+
}
91+
92+
/** @return Bounce[] */
93+
public function findByStatus(string $status): array
94+
{
95+
return $this->bounceRepository->findByStatus($status);
96+
}
97+
98+
public function getUserMessageBounceCount(): int
99+
{
100+
return $this->userMessageBounceRepository->count();
101+
}
102+
103+
/**
104+
* @return array<int, array{umb: UserMessageBounce, bounce: Bounce}>
105+
*/ public function fetchUserMessageBounceBatch(int $fromId, int $batchSize): array
106+
{
107+
return $this->userMessageBounceRepository->getPaginatedWithJoinNoRelation($fromId, $batchSize);
108+
}
62109
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Service\Manager;
6+
7+
use PhpList\Core\Domain\Messaging\Model\Bounce;
8+
use PhpList\Core\Domain\Messaging\Model\BounceRegex;
9+
use PhpList\Core\Domain\Messaging\Model\BounceRegexBounce;
10+
use PhpList\Core\Domain\Messaging\Repository\BounceRegexBounceRepository;
11+
use PhpList\Core\Domain\Messaging\Repository\BounceRegexRepository;
12+
13+
class BounceRuleManager
14+
{
15+
public function __construct(
16+
private readonly BounceRegexRepository $repository,
17+
private readonly BounceRegexBounceRepository $bounceRegexBounceRepository
18+
) {
19+
}
20+
21+
/**
22+
* @return array<string,BounceRegex>
23+
*/
24+
public function loadActiveRules(): array
25+
{
26+
return $this->mapRows($this->repository->fetchActiveOrdered());
27+
}
28+
29+
/**
30+
* @return array<string,BounceRegex>
31+
*/
32+
public function loadAllRules(): array
33+
{
34+
return $this->mapRows($this->repository->fetchAllOrdered());
35+
}
36+
37+
/**
38+
* Internal helper to normalize repository rows into the legacy shape.
39+
*
40+
* @param BounceRegex[] $rows
41+
* @return array<string,BounceRegex>
42+
*/
43+
private function mapRows(array $rows): array
44+
{
45+
$result = [];
46+
47+
foreach ($rows as $row) {
48+
$regex = $row->getRegex();
49+
$action = $row->getAction();
50+
$id = $row->getId();
51+
52+
if (
53+
!is_string($regex)
54+
|| $regex === ''
55+
|| !is_string($action)
56+
|| $action === ''
57+
|| !is_int($id)
58+
) {
59+
continue;
60+
}
61+
62+
$result[$regex] = $row;
63+
}
64+
65+
return $result;
66+
}
67+
68+
69+
/**
70+
* @param array<string,BounceRegex> $rules
71+
*/
72+
public function matchBounceRules(string $text, array $rules): ?BounceRegex
73+
{
74+
foreach ($rules as $pattern => $rule) {
75+
$pattern = str_replace(' ', '\s+', $pattern);
76+
if (@preg_match('/'.preg_quote($pattern).'/iUm', $text)) {
77+
return $rule;
78+
} elseif (@preg_match('/'.$pattern.'/iUm', $text)) {
79+
return $rule;
80+
}
81+
}
82+
83+
return null;
84+
}
85+
86+
public function incrementCount(BounceRegex $rule): void
87+
{
88+
$rule->setCount($rule->getCount() + 1);
89+
90+
$this->repository->save($rule);
91+
}
92+
93+
public function linkRuleToBounce(BounceRegex $rule, Bounce $bounce): BounceregexBounce
94+
{
95+
$relation = new BounceRegexBounce($rule->getId(), $bounce->getId());
96+
$this->bounceRegexBounceRepository->save($relation);
97+
98+
return $relation;
99+
}
100+
}

0 commit comments

Comments
 (0)