diff --git a/config/services/managers.yml b/config/services/managers.yml index 0e1b1d8a..d253fc95 100644 --- a/config/services/managers.yml +++ b/config/services/managers.yml @@ -71,3 +71,7 @@ services: PhpList\Core\Domain\Subscription\Service\Manager\SubscriberBlacklistManager: autowire: true autoconfigure: true + + PhpList\Core\Domain\Subscription\Service\Manager\SubscribePageManager: + autowire: true + autoconfigure: true diff --git a/config/services/repositories.yml b/config/services/repositories.yml index db3831dd..02c9e7d3 100644 --- a/config/services/repositories.yml +++ b/config/services/repositories.yml @@ -120,3 +120,13 @@ services: parent: PhpList\Core\Domain\Common\Repository\AbstractRepository arguments: - PhpList\Core\Domain\Subscription\Model\UserBlacklistData + + PhpList\Core\Domain\Subscription\Repository\SubscriberPageRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Subscription\Model\SubscribePage + + PhpList\Core\Domain\Subscription\Repository\SubscriberPageDataRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Subscription\Model\SubscribePageData diff --git a/src/Domain/Subscription/Model/SubscribePage.php b/src/Domain/Subscription/Model/SubscribePage.php index 7ec518b2..e4696380 100644 --- a/src/Domain/Subscription/Model/SubscribePage.php +++ b/src/Domain/Subscription/Model/SubscribePage.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Mapping as ORM; use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel; use PhpList\Core\Domain\Common\Model\Interfaces\Identity; +use PhpList\Core\Domain\Identity\Model\Administrator; use PhpList\Core\Domain\Subscription\Repository\SubscriberPageRepository; #[ORM\Entity(repositoryClass: SubscriberPageRepository::class)] @@ -24,8 +25,9 @@ class SubscribePage implements DomainModel, Identity #[ORM\Column(name: 'active', type: 'boolean', options: ['default' => 0])] private bool $active = false; - #[ORM\Column(name: 'owner', type: 'integer', nullable: true)] - private ?int $owner = null; + #[ORM\ManyToOne(targetEntity: Administrator::class)] + #[ORM\JoinColumn(name: 'owner', referencedColumnName: 'id', nullable: true)] + private ?Administrator $owner = null; public function getId(): ?int { @@ -42,7 +44,7 @@ public function isActive(): bool return $this->active; } - public function getOwner(): ?int + public function getOwner(): ?Administrator { return $this->owner; } @@ -59,7 +61,7 @@ public function setActive(bool $active): self return $this; } - public function setOwner(?int $owner): self + public function setOwner(?Administrator $owner): self { $this->owner = $owner; return $this; diff --git a/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php b/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php index 565930d4..68d0d6bc 100644 --- a/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php +++ b/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php @@ -7,8 +7,21 @@ use PhpList\Core\Domain\Common\Repository\AbstractRepository; use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait; use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface; +use PhpList\Core\Domain\Subscription\Model\SubscribePage; +use PhpList\Core\Domain\Subscription\Model\SubscribePageData; class SubscriberPageDataRepository extends AbstractRepository implements PaginatableRepositoryInterface { use CursorPaginationTrait; + + public function findByPageAndName(SubscribePage $page, string $name): ?SubscribePageData + { + return $this->findOneBy(['id' => $page->getId(), 'name' => $name]); + } + + /** @return SubscribePageData[] */ + public function getByPage(SubscribePage $page): array + { + return $this->findBy(['id' => $page->getId()]); + } } diff --git a/src/Domain/Subscription/Repository/SubscriberPageRepository.php b/src/Domain/Subscription/Repository/SubscriberPageRepository.php index 2a8383c0..136b589c 100644 --- a/src/Domain/Subscription/Repository/SubscriberPageRepository.php +++ b/src/Domain/Subscription/Repository/SubscriberPageRepository.php @@ -7,8 +7,24 @@ use PhpList\Core\Domain\Common\Repository\AbstractRepository; use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait; use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface; +use PhpList\Core\Domain\Subscription\Model\SubscribePage; +use PhpList\Core\Domain\Subscription\Model\SubscribePageData; class SubscriberPageRepository extends AbstractRepository implements PaginatableRepositoryInterface { use CursorPaginationTrait; + + /** @return array{page: SubscribePage, data: SubscribePageData}[] */ + public function findPagesWithData(int $pageId): array + { + return $this->createQueryBuilder('p') + ->select('p AS page, d AS data') + ->from(SubscribePage::class, 'p') + ->from(SubscribePageData::class, 'd') + ->where('p.id = :id') + ->andWhere('d.id = p.id') + ->setParameter('id', $pageId) + ->getQuery() + ->getResult(); + } } diff --git a/src/Domain/Subscription/Service/Manager/SubscribePageManager.php b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php new file mode 100644 index 00000000..8e429dc4 --- /dev/null +++ b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php @@ -0,0 +1,105 @@ +setTitle($title) + ->setActive($active) + ->setOwner($owner); + + $this->pageRepository->save($page); + + return $page; + } + + public function getPage(int $id): SubscribePage + { + /** @var SubscribePage|null $page */ + $page = $this->pageRepository->find($id); + if (!$page) { + throw new NotFoundHttpException('Subscribe page not found'); + } + + return $page; + } + + public function updatePage( + SubscribePage $page, + ?string $title = null, + ?bool $active = null, + ?Administrator $owner = null + ): SubscribePage { + if ($title !== null) { + $page->setTitle($title); + } + if ($active !== null) { + $page->setActive($active); + } + if ($owner !== null) { + $page->setOwner($owner); + } + + $this->entityManager->flush(); + + return $page; + } + + public function setActive(SubscribePage $page, bool $active): void + { + $page->setActive($active); + $this->entityManager->flush(); + } + + public function deletePage(SubscribePage $page): void + { + $this->pageRepository->remove($page); + } + + /** @return SubscribePageData[] */ + public function getPageData(SubscribePage $page): array + { + return $this->pageDataRepository->getByPage($page,); + } + + public function setPageData(SubscribePage $page, string $name, ?string $value): SubscribePageData + { + /** @var SubscribePageData|null $data */ + $data = $this->pageDataRepository->findByPageAndName($page, $name); + + if (!$data) { + $data = (new SubscribePageData()) + ->setId((int)$page->getId()) + ->setName($name); + $this->entityManager->persist($data); + } + + $data->setData($value); + $this->entityManager->flush(); + + return $data; + } +} diff --git a/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php b/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php new file mode 100644 index 00000000..422c78a7 --- /dev/null +++ b/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php @@ -0,0 +1,234 @@ +pageRepository = $this->createMock(SubscriberPageRepository::class); + $this->pageDataRepository = $this->createMock(SubscriberPageDataRepository::class); + $this->entityManager = $this->createMock(EntityManagerInterface::class); + + $this->manager = new SubscribePageManager( + pageRepository: $this->pageRepository, + pageDataRepository: $this->pageDataRepository, + entityManager: $this->entityManager, + ); + } + + public function testCreatePageCreatesAndSaves(): void + { + $owner = new Administrator(); + $this->pageRepository + ->expects($this->once()) + ->method('save') + ->with($this->isInstanceOf(SubscribePage::class)); + + $page = $this->manager->createPage('My Page', true, $owner); + + $this->assertInstanceOf(SubscribePage::class, $page); + $this->assertSame('My Page', $page->getTitle()); + $this->assertTrue($page->isActive()); + $this->assertSame($owner, $page->getOwner()); + } + + public function testGetPageReturnsPage(): void + { + $page = new SubscribePage(); + $this->pageRepository + ->expects($this->once()) + ->method('find') + ->with(123) + ->willReturn($page); + + $result = $this->manager->getPage(123); + + $this->assertSame($page, $result); + } + + public function testGetPageThrowsWhenNotFound(): void + { + $this->pageRepository + ->expects($this->once()) + ->method('find') + ->with(999) + ->willReturn(null); + + $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('Subscribe page not found'); + + $this->manager->getPage(999); + } + + public function testUpdatePageUpdatesProvidedFieldsAndFlushes(): void + { + $originalOwner = new Administrator(); + $newOwner = new Administrator(); + $page = (new SubscribePage()) + ->setTitle('Old Title') + ->setActive(false) + ->setOwner($originalOwner); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $updated = $this->manager->updatePage($page, title: 'New Title', active: true, owner: $newOwner); + + $this->assertSame($page, $updated); + $this->assertSame('New Title', $updated->getTitle()); + $this->assertTrue($updated->isActive()); + $this->assertSame($newOwner, $updated->getOwner()); + } + + public function testUpdatePageLeavesNullFieldsUntouched(): void + { + $owner = new Administrator(); + $page = (new SubscribePage()) + ->setTitle('Keep Title') + ->setActive(true) + ->setOwner($owner); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $updated = $this->manager->updatePage(page: $page, title: null, active: null, owner: null); + + $this->assertSame('Keep Title', $updated->getTitle()); + $this->assertTrue($updated->isActive()); + $this->assertSame($owner, $updated->getOwner()); + } + + public function testSetActiveSetsFlagAndFlushes(): void + { + $page = (new SubscribePage()) + ->setTitle('Any') + ->setActive(false); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $this->manager->setActive($page, true); + $this->assertTrue($page->isActive()); + } + + public function testDeletePageCallsRepositoryRemove(): void + { + $page = new SubscribePage(); + + $this->pageRepository + ->expects($this->once()) + ->method('remove') + ->with($page); + + $this->manager->deletePage($page); + } + + public function testGetPageDataReturnsStringWhenFound(): void + { + $page = new SubscribePage(); + $data = $this->createMock(SubscribePageData::class); + $data->expects($this->once())->method('getData')->willReturn('value'); + + $this->pageDataRepository + ->expects($this->once()) + ->method('getByPage') + ->with($page) + ->willReturn([$data]); + + $result = $this->manager->getPageData($page); + $this->assertIsArray($result); + $this->assertSame('value', $result[0]->getData()); + } + + public function testGetPageDataReturnsNullWhenNotFound(): void + { + $page = new SubscribePage(); + + $this->pageDataRepository + ->expects($this->once()) + ->method('getByPage') + ->with($page) + ->willReturn([]); + + $result = $this->manager->getPageData($page); + $this->assertEmpty($result); + } + + public function testSetPageDataUpdatesExistingDataAndFlushes(): void + { + $page = new SubscribePage(); + $existing = new SubscribePageData(); + $existing->setId(5)->setName('color')->setData('red'); + + $this->pageDataRepository + ->expects($this->once()) + ->method('findByPageAndName') + ->with($page, 'color') + ->willReturn($existing); + + $this->entityManager + ->expects($this->never()) + ->method('persist'); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $result = $this->manager->setPageData($page, 'color', 'blue'); + + $this->assertSame($existing, $result); + $this->assertSame('blue', $result->getData()); + } + + public function testSetPageDataCreatesNewWhenMissingAndPersistsAndFlushes(): void + { + $page = $this->getMockBuilder(SubscribePage::class) + ->onlyMethods(['getId']) + ->getMock(); + $page->method('getId')->willReturn(123); + + $this->pageDataRepository + ->expects($this->once()) + ->method('findByPageAndName') + ->with($page, 'greeting') + ->willReturn(null); + + $this->entityManager + ->expects($this->once()) + ->method('persist') + ->with($this->isInstanceOf(SubscribePageData::class)); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $result = $this->manager->setPageData($page, 'greeting', 'hello'); + + $this->assertInstanceOf(SubscribePageData::class, $result); + $this->assertSame(123, $result->getId()); + $this->assertSame('greeting', $result->getName()); + $this->assertSame('hello', $result->getData()); + } +}