Skip to content

ISSUE-122: delete leaving blacklisted #343

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

Merged
merged 2 commits into from
Jun 20, 2025
Merged
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
5 changes: 5 additions & 0 deletions config/services/managers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ services:
PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService:
autowire: true
autoconfigure: true
public: true
15 changes: 15 additions & 0 deletions config/services/repositories.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,18 @@ services:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Analytics\Model\UserMessageView

PhpList\Core\Domain\Analytics\Repository\LinkTrackUmlClickRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Analytics\Model\LinkTrackUmlClick

PhpList\Core\Domain\Messaging\Repository\UserMessageRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Messaging\Model\UserMessage

PhpList\Core\Domain\Subscription\Repository\SubscriberHistoryRepository:
parent: PhpList\Core\Domain\Common\Repository\AbstractRepository
arguments:
- PhpList\Core\Domain\Subscription\Model\SubscriberHistory
13 changes: 7 additions & 6 deletions src/Domain/Messaging/Model/UserMessageBounce.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class UserMessageBounce implements DomainModel, Identity
private ?int $id = null;

#[ORM\Column(name: 'user', type: 'integer')]
private int $user;
private int $userId;

#[ORM\Column(name: 'message', type: 'integer')]
private int $messageId;
Expand All @@ -36,8 +36,9 @@ class UserMessageBounce implements DomainModel, Identity
#[ORM\Column(name: 'time', type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])]
private DateTime $createdAt;

public function __construct()
public function __construct(int $bounce)
{
$this->bounce = $bounce;
$this->createdAt = new DateTime();
}

Expand All @@ -46,9 +47,9 @@ public function getId(): ?int
return $this->id;
}

public function getUser(): int
public function getUserId(): int
{
return $this->user;
return $this->userId;
}

public function getMessageId(): int
Expand All @@ -66,9 +67,9 @@ public function getCreatedAt(): DateTime
return $this->createdAt;
}

public function setUser(int $user): self
public function setUserId(int $userId): self
{
$this->user = $user;
$this->userId = $userId;
return $this;
}

Expand Down
10 changes: 5 additions & 5 deletions src/Domain/Messaging/Model/UserMessageForward.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class UserMessageForward implements DomainModel, Identity
private ?int $id = null;

#[ORM\Column(name: 'user', type: 'integer')]
private int $user;
private int $userId;

#[ORM\Column(name: 'message', type: 'integer')]
private int $messageId;
Expand All @@ -47,9 +47,9 @@ public function getId(): ?int
return $this->id;
}

public function getUser(): int
public function getUserId(): int
{
return $this->user;
return $this->userId;
}

public function getMessageId(): int
Expand All @@ -72,9 +72,9 @@ public function getCreatedAt(): DateTime
return $this->createdAt;
}

public function setUser(int $user): self
public function setUserId(int $userId): self
{
$this->user = $user;
$this->userId = $userId;
return $this;
}

Expand Down
8 changes: 6 additions & 2 deletions src/Domain/Subscription/Service/Manager/SubscriberManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PhpList\Core\Domain\Subscription\Model\Dto\UpdateSubscriberDto;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository;
use PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;

Expand All @@ -19,15 +20,18 @@ class SubscriberManager
private SubscriberRepository $subscriberRepository;
private EntityManagerInterface $entityManager;
private MessageBusInterface $messageBus;
private SubscriberDeletionService $subscriberDeletionService;

public function __construct(
SubscriberRepository $subscriberRepository,
EntityManagerInterface $entityManager,
MessageBusInterface $messageBus
MessageBusInterface $messageBus,
SubscriberDeletionService $subscriberDeletionService
) {
$this->subscriberRepository = $subscriberRepository;
$this->entityManager = $entityManager;
$this->messageBus = $messageBus;
$this->subscriberDeletionService = $subscriberDeletionService;
}

public function createSubscriber(CreateSubscriberDto $subscriberDto): Subscriber
Expand Down Expand Up @@ -90,7 +94,7 @@ public function updateSubscriber(UpdateSubscriberDto $subscriberDto): Subscriber

public function deleteSubscriber(Subscriber $subscriber): void
{
$this->subscriberRepository->remove($subscriber);
$this->subscriberDeletionService->deleteLeavingBlacklist($subscriber);
}

public function createFromImport(ImportSubscriberDto $subscriberDto): Subscriber
Expand Down
77 changes: 77 additions & 0 deletions src/Domain/Subscription/Service/SubscriberDeletionService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Domain\Subscription\Service;

use Doctrine\ORM\EntityManagerInterface;
use PhpList\Core\Domain\Analytics\Repository\LinkTrackUmlClickRepository;
use PhpList\Core\Domain\Analytics\Repository\UserMessageViewRepository;
use PhpList\Core\Domain\Messaging\Repository\UserMessageBounceRepository;
use PhpList\Core\Domain\Messaging\Repository\UserMessageForwardRepository;
use PhpList\Core\Domain\Messaging\Repository\UserMessageRepository;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Repository\SubscriberAttributeValueRepository;
use PhpList\Core\Domain\Subscription\Repository\SubscriberHistoryRepository;
use PhpList\Core\Domain\Subscription\Repository\SubscriptionRepository;

class SubscriberDeletionService
{
private LinkTrackUmlClickRepository $linkTrackUmlClickRepo;
private EntityManagerInterface $entityManager;
private UserMessageRepository $userMessageRepo;
private SubscriberAttributeValueRepository $subscriberAttrValueRepo;
private SubscriberHistoryRepository $subscriberHistoryRepo;
private UserMessageBounceRepository $userMessageBounceRepo;
private UserMessageForwardRepository $userMessageForwardRepo;
private UserMessageViewRepository $userMessageViewRepo;
private SubscriptionRepository $subscriptionRepo;

public function __construct(
LinkTrackUmlClickRepository $linkTrackUmlClickRepo,
EntityManagerInterface $entityManager,
UserMessageRepository $userMessageRepo,
SubscriberAttributeValueRepository $subscriberAttrValueRepo,
SubscriberHistoryRepository $subscriberHistoryRepo,
UserMessageBounceRepository $userMessageBounceRepo,
UserMessageForwardRepository $userMessageForwardRepo,
UserMessageViewRepository $userMessageViewRepo,
SubscriptionRepository $subscriptionRepo,
) {
$this->linkTrackUmlClickRepo = $linkTrackUmlClickRepo;
$this->entityManager = $entityManager;
$this->userMessageRepo = $userMessageRepo;
$this->subscriberAttrValueRepo = $subscriberAttrValueRepo;
$this->subscriberHistoryRepo = $subscriberHistoryRepo;
$this->userMessageBounceRepo = $userMessageBounceRepo;
$this->userMessageForwardRepo = $userMessageForwardRepo;
$this->userMessageViewRepo = $userMessageViewRepo;
$this->subscriptionRepo = $subscriptionRepo;
}

public function deleteLeavingBlacklist(Subscriber $subscriber): void
{
$this->removeEntities($this->linkTrackUmlClickRepo->findBy(['userId' => $subscriber->getId()]));
$this->removeEntities($this->subscriptionRepo->findBy(['subscriber' => $subscriber]));
$this->removeEntities($this->userMessageRepo->findBy(['user' => $subscriber]));
$this->removeEntities($this->subscriberAttrValueRepo->findBy(['subscriber' => $subscriber]));
$this->removeEntities($this->subscriberHistoryRepo->findBy(['subscriber' => $subscriber]));
$this->removeEntities($this->userMessageBounceRepo->findBy(['userId' => $subscriber->getId()]));
$this->removeEntities($this->userMessageForwardRepo->findBy(['userId' => $subscriber->getId()]));
$this->removeEntities($this->userMessageViewRepo->findBy(['userId' => $subscriber->getId()]));

$this->entityManager->remove($subscriber);
}

/**
* Remove a collection of entities
*
* @param array $entities
*/
private function removeEntities(array $entities): void
{
foreach ($entities as $entity) {
$this->entityManager->remove($entity);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Tests\Integration\Domain\Subscription\Service;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
use Exception;
use PhpList\Core\Domain\Analytics\Model\LinkTrackUmlClick;
use PhpList\Core\Domain\Analytics\Model\UserMessageView;
use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Domain\Messaging\Model\Message;
use PhpList\Core\Domain\Messaging\Model\Message\MessageContent;
use PhpList\Core\Domain\Messaging\Model\Message\MessageFormat;
use PhpList\Core\Domain\Messaging\Model\Message\MessageMetadata;
use PhpList\Core\Domain\Messaging\Model\Message\MessageOptions;
use PhpList\Core\Domain\Messaging\Model\Message\MessageSchedule;
use PhpList\Core\Domain\Messaging\Model\UserMessage;
use PhpList\Core\Domain\Messaging\Model\UserMessageBounce;
use PhpList\Core\Domain\Messaging\Model\UserMessageForward;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
use PhpList\Core\Domain\Subscription\Model\Subscription;
use PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService;
use PhpList\Core\TestingSupport\Traits\DatabaseTestTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class SubscriberDeletionServiceTest extends KernelTestCase
{
use DatabaseTestTrait;

private ?SubscriberDeletionService $subscriberDeletionService = null;
protected ?EntityManagerInterface $entityManager = null;

protected function setUp(): void
{
parent::setUp();
$this->loadSchema();

$this->subscriberDeletionService = self::getContainer()->get(SubscriberDeletionService::class);
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
}

protected function tearDown(): void
{
$schemaTool = new SchemaTool($this->entityManager);
$schemaTool->dropDatabase();
parent::tearDown();
}

public function testDeleteSubscriberWithRelatedDataDoesNotThrowDoctrineError(): void
{
$admin = new Administrator();
$this->entityManager->persist($admin);

$msg = new Message(
format: new MessageFormat(true, MessageFormat::FORMAT_TEXT),
schedule: new MessageSchedule(1, null, 3, null, null),
metadata: new MessageMetadata('done'),
content: new MessageContent('Owned by Admin 1!'),
options: new MessageOptions(),
owner: $admin
);
$this->entityManager->persist($msg);

$subscriber = new Subscriber();
$subscriber->setEmail('[email protected]');
$subscriber->setConfirmed(true);
$subscriber->setHtmlEmail(true);
$subscriber->setBlacklisted(false);
$subscriber->setDisabled(false);
$this->entityManager->persist($subscriber);
$this->entityManager->flush();

$subscriberId = $subscriber->getId();
$this->assertNotNull($subscriberId, 'Subscriber ID should not be null');

$subscriberList = new SubscriberList();
$subscriberList->setDescription('Test List Description');
$this->entityManager->persist($subscriberList);

$subscription = new Subscription();
$subscription->setSubscriber($subscriber);
$subscription->setSubscriberList($subscriberList);
$this->entityManager->persist($subscription);

$linkTrackUmlClick = new LinkTrackUmlClick();
$linkTrackUmlClick->setMessageId(1);
$linkTrackUmlClick->setUserId($subscriberId);
$this->entityManager->persist($linkTrackUmlClick);

$userMessage = new UserMessage($subscriber, $msg);
$userMessage->setStatus('sent');
$this->entityManager->persist($userMessage);

$userMessageBounce = new UserMessageBounce(1);
$userMessageBounce->setUserId($subscriberId);
$userMessageBounce->setMessageId(1);
$this->entityManager->persist($userMessageBounce);

$userMessageForward = new UserMessageForward();
$userMessageForward->setUserId($subscriberId);
$userMessageForward->setMessageId(1);
$this->entityManager->persist($userMessageForward);

$userMessageView = new UserMessageView();
$userMessageView->setMessageId(1);
$userMessageView->setUserid($subscriberId);
$this->entityManager->persist($userMessageView);

$this->entityManager->flush();

try {
$this->subscriberDeletionService->deleteLeavingBlacklist($subscriber);
$this->entityManager->flush();
$this->assertTrue(true, 'No exception was thrown');
} catch (Exception $e) {
$this->fail('Exception was thrown: ' . $e->getMessage());
}

$deletedSubscriber = $this->entityManager->getRepository(Subscriber::class)->find($subscriberId);
$this->assertNull($deletedSubscriber, 'Subscriber should be deleted');

$subscriptionRepo = $this->entityManager->getRepository(Subscription::class);
$subscriptions = $subscriptionRepo->findBy(['subscriber' => $subscriber]);
$this->assertEmpty($subscriptions, 'Subscriptions should be deleted');

$linkTrackRepo = $this->entityManager->getRepository(LinkTrackUmlClick::class);
$linkTrackUmlClicks = $linkTrackRepo->findBy(['userId' => $subscriberId]);
$this->assertEmpty($linkTrackUmlClicks, 'LinkTrackUmlClicks should be deleted');

$userMessageRepo = $this->entityManager->getRepository(UserMessage::class);
$userMessages = $userMessageRepo->findBy(['user' => $subscriber]);
$this->assertEmpty($userMessages, 'UserMessages should be deleted');

$bounceRepo = $this->entityManager->getRepository(UserMessageBounce::class);
$userMessageBounces = $bounceRepo->findBy(['userId' => $subscriberId]);
$this->assertEmpty($userMessageBounces, 'UserMessageBounces should be deleted');

$forwardRepo = $this->entityManager->getRepository(UserMessageForward::class);
$userMessageForwards = $forwardRepo->findBy(['userId' => $subscriberId]);
$this->assertEmpty($userMessageForwards, 'UserMessageForwards should be deleted');

$viewRepo = $this->entityManager->getRepository(UserMessageView::class);
$userMessageViews = $viewRepo->findBy(['userId' => $subscriberId]);
$this->assertEmpty($userMessageViews, 'UserMessageViews should be deleted');
}
}
Loading
Loading