Skip to content

Commit 4f478a9

Browse files
committed
Process send campaigns queue
1 parent d21e99d commit 4f478a9

File tree

15 files changed

+840
-6
lines changed

15 files changed

+840
-6
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"symfony/mailchimp-mailer": "^6.4",
6767
"symfony/sendgrid-mailer": "^6.4",
6868
"symfony/twig-bundle": "^6.4",
69-
"symfony/messenger": "^6.4"
69+
"symfony/messenger": "^6.4",
70+
"symfony/lock": "^6.4"
7071
},
7172
"require-dev": {
7273
"phpunit/phpunit": "^9.5",

config/services/managers.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,3 @@ services:
5555
PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager:
5656
autowire: true
5757
autoconfigure: true
58-
59-
PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService:
60-
autowire: true
61-
autoconfigure: true
62-
public: true

config/services/providers.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
services:
2+
PhpList\Core\Domain\Subscription\Service\Provider\SubscriberProvider:
3+
autowire: true
4+
autoconfigure: true

config/services/repositories.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,8 @@ services:
100100
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
101101
arguments:
102102
- PhpList\Core\Domain\Subscription\Model\SubscriberHistory
103+
104+
PhpList\Core\Domain\Messaging\Repository\ListMessageRepository:
105+
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
106+
arguments:
107+
- PhpList\Core\Domain\Subscription\Model\ListMessage

config/services/services.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ services:
1414
autoconfigure: true
1515
arguments:
1616
$defaultFromEmail: '%app.mailer_from%'
17+
18+
PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService:
19+
autowire: true
20+
autoconfigure: true
21+
public: true
22+
23+
PhpList\Core\Domain\Messaging\Service\MessageProcessingPreparator:
24+
autowire: true
25+
autoconfigure: true
26+
public: true
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Command;
6+
7+
use Symfony\Component\Console\Command\Command;
8+
use Symfony\Component\Console\Input\InputInterface;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
use Doctrine\ORM\EntityManagerInterface;
11+
use PhpList\Core\Domain\Messaging\Model\Message;
12+
use PhpList\Core\Domain\Messaging\Repository\MessageRepository;
13+
use PhpList\Core\Domain\Messaging\Service\MessageProcessingPreparator;
14+
use PhpList\Core\Domain\Subscription\Service\Provider\SubscriberProvider;
15+
use Symfony\Component\Lock\LockFactory;
16+
use Symfony\Component\Mailer\MailerInterface;
17+
use Symfony\Component\Mime\Email;
18+
use Throwable;
19+
20+
class ProcessQueueCommand extends Command
21+
{
22+
protected static $defaultName = 'phplist:process-queue';
23+
24+
private MessageRepository $messageRepository;
25+
private MailerInterface $mailer;
26+
private LockFactory $lockFactory;
27+
private EntityManagerInterface $entityManager;
28+
private SubscriberProvider $subscriberProvider;
29+
private MessageProcessingPreparator $messageProcessingPreparator;
30+
31+
public function __construct(
32+
MessageRepository $messageRepository,
33+
MailerInterface $mailer,
34+
LockFactory $lockFactory,
35+
EntityManagerInterface $entityManager,
36+
SubscriberProvider $subscriberProvider,
37+
MessageProcessingPreparator $messageProcessingPreparator
38+
) {
39+
parent::__construct();
40+
$this->messageRepository = $messageRepository;
41+
$this->mailer = $mailer;
42+
$this->lockFactory = $lockFactory;
43+
$this->entityManager = $entityManager;
44+
$this->subscriberProvider = $subscriberProvider;
45+
$this->messageProcessingPreparator = $messageProcessingPreparator;
46+
}
47+
48+
/**
49+
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
50+
*/
51+
protected function execute(InputInterface $input, OutputInterface $output): int
52+
{
53+
$lock = $this->lockFactory->createLock('queue_processor');
54+
if (!$lock->acquire()) {
55+
$output->writeln('Queue is already being processed by another instance.');
56+
57+
return Command::FAILURE;
58+
}
59+
60+
try {
61+
$this->messageProcessingPreparator->ensureSubscribersHaveUuid($output);
62+
$this->messageProcessingPreparator->ensureCampaignsHaveUuid($output);
63+
64+
$campaigns = $this->messageRepository->findBy(['status' => 'submitted']);
65+
66+
foreach ($campaigns as $campaign) {
67+
$this->processCampaign($campaign, $output);
68+
}
69+
} finally {
70+
$lock->release();
71+
}
72+
73+
return Command::SUCCESS;
74+
}
75+
76+
private function processCampaign(Message $campaign, OutputInterface $output): void
77+
{
78+
$subscribers = $this->subscriberProvider->getSubscribersForMessage($campaign);
79+
// todo: check $ISPrestrictions logic
80+
foreach ($subscribers as $subscriber) {
81+
if (!filter_var($subscriber->getEmail(), FILTER_VALIDATE_EMAIL)) {
82+
continue;
83+
}
84+
85+
$email = (new Email())
86+
87+
->to($subscriber->getEmail())
88+
->subject($campaign->getContent()->getSubject())
89+
->text($campaign->getContent()->getTextMessage())
90+
->html($campaign->getContent()->getText());
91+
92+
try {
93+
$this->mailer->send($email);
94+
95+
// todo: log somewhere that this subscriber got email
96+
} catch (Throwable $e) {
97+
$output->writeln('Failed to send to: ' . $subscriber->getEmail());
98+
}
99+
100+
usleep(100000);
101+
}
102+
103+
$campaign->getMetadata()->setStatus('sent');
104+
$this->entityManager->flush();
105+
}
106+
}

src/Domain/Messaging/Model/Message.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ public function getUuid(): ?string
125125
return $this->uuid;
126126
}
127127

128+
public function setUuid(string $uuid): self
129+
{
130+
$this->uuid = $uuid;
131+
return $this;
132+
}
133+
128134
public function getOwner(): ?Administrator
129135
{
130136
return $this->owner;

src/Domain/Messaging/Repository/ListMessageRepository.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,15 @@
1111
class ListMessageRepository extends AbstractRepository implements PaginatableRepositoryInterface
1212
{
1313
use CursorPaginationTrait;
14+
15+
/** @return int[] */
16+
public function getListIdsByMessageId(int $messageId): array
17+
{
18+
return $this->createQueryBuilder('lm')
19+
->select('IDENTITY(lm.list)')
20+
->where('lm.messageId = :messageId')
21+
->setParameter('messageId', $messageId)
22+
->getQuery()
23+
->getSingleColumnResult();
24+
}
1425
}

src/Domain/Messaging/Repository/MessageRepository.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212

1313
class MessageRepository extends AbstractRepository implements PaginatableRepositoryInterface
1414
{
15+
/**
16+
* @return Message[]
17+
*/
18+
public function findCampaignsWithoutUuid(): array
19+
{
20+
return $this->createQueryBuilder('m')
21+
->where('m.uuid IS NULL OR m.uuid = :emptyString')
22+
->setParameter('emptyString', '')
23+
->getQuery()
24+
->getResult();
25+
}
26+
1527
public function getByOwnerId(int $ownerId): array
1628
{
1729
return $this->createQueryBuilder('m')
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Service;
6+
7+
use Doctrine\ORM\EntityManagerInterface;
8+
use PhpList\Core\Domain\Messaging\Repository\MessageRepository;
9+
use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
12+
class MessageProcessingPreparator
13+
{
14+
private EntityManagerInterface $entityManager;
15+
private SubscriberRepository $subscriberRepository;
16+
private MessageRepository $messageRepository;
17+
18+
public function __construct(
19+
EntityManagerInterface $entityManager,
20+
SubscriberRepository $subscriberRepository,
21+
MessageRepository $messageRepository
22+
) {
23+
$this->entityManager = $entityManager;
24+
$this->subscriberRepository = $subscriberRepository;
25+
$this->messageRepository = $messageRepository;
26+
}
27+
28+
public function ensureSubscribersHaveUuid(OutputInterface $output): void
29+
{
30+
$subscribersWithoutUuid = $this->subscriberRepository->findSubscribersWithoutUuid();
31+
32+
$numSubscribers = count($subscribersWithoutUuid);
33+
if ($numSubscribers > 0) {
34+
$output->writeln(sprintf('Giving a UUID to %d subscribers, this may take a while', $numSubscribers));
35+
foreach ($subscribersWithoutUuid as $subscriber) {
36+
$subscriber->setUniqueId(bin2hex(random_bytes(16)));
37+
}
38+
$this->entityManager->flush();
39+
}
40+
}
41+
42+
public function ensureCampaignsHaveUuid(OutputInterface $output): void
43+
{
44+
$campaignsWithoutUuid = $this->messageRepository->findCampaignsWithoutUuid();
45+
46+
$numCampaigns = count($campaignsWithoutUuid);
47+
if ($numCampaigns > 0) {
48+
$output->writeln(sprintf('Giving a UUID to %d campaigns', $numCampaigns));
49+
foreach ($campaignsWithoutUuid as $campaign) {
50+
$campaign->setUuid(bin2hex(random_bytes(18)));
51+
}
52+
$this->entityManager->flush();
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)