From 1225b55284221249e7b4646688ab98f7cb21abf9 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Tue, 12 Aug 2025 20:36:00 +0400 Subject: [PATCH 1/5] subscriber page manager --- config/services/managers.yml | 4 + config/services/repositories.yml | 10 ++ .../Service/Manager/SubscribePageManager.php | 98 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/Domain/Subscription/Service/Manager/SubscribePageManager.php 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/Service/Manager/SubscribePageManager.php b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php new file mode 100644 index 00000000..1332462b --- /dev/null +++ b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php @@ -0,0 +1,98 @@ +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, ?int $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); + } + + public function getPageData(SubscribePage $page, string $name): ?string + { + /** @var SubscribePageData|null $data */ + $data = $this->pageDataRepository->findOneBy(['id' => $page->getId(), 'name' => $name]); + return $data?->getData(); + } + + public function setPageData(SubscribePage $page, string $name, ?string $value): SubscribePageData + { + /** @var SubscribePageData|null $data */ + $data = $this->pageDataRepository->findOneBy(['id' => $page->getId(), 'name' => $name]); + + if (!$data) { + $data = (new SubscribePageData()) + ->setId((int)$page->getId()) + ->setName($name); + $this->entityManager->persist($data); + } + + $data->setData($value); + $this->entityManager->flush(); + + return $data; + } +} From e91742cc4f0c582bb287ebf7404f1f96d27d3490 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Tue, 12 Aug 2025 20:50:18 +0400 Subject: [PATCH 2/5] owner entity --- src/Domain/Subscription/Model/SubscribePage.php | 10 ++++++---- .../Repository/SubscriberPageDataRepository.php | 7 +++++++ .../Service/Manager/SubscribePageManager.php | 15 ++++++++++----- 3 files changed, 23 insertions(+), 9 deletions(-) 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..504174a6 100644 --- a/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php +++ b/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php @@ -7,8 +7,15 @@ 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]); + } } diff --git a/src/Domain/Subscription/Service/Manager/SubscribePageManager.php b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php index 1332462b..a21050eb 100644 --- a/src/Domain/Subscription/Service/Manager/SubscribePageManager.php +++ b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php @@ -5,6 +5,7 @@ namespace PhpList\Core\Domain\Subscription\Service\Manager; use Doctrine\ORM\EntityManagerInterface; +use PhpList\Core\Domain\Identity\Model\Administrator; use PhpList\Core\Domain\Subscription\Model\SubscribePage; use PhpList\Core\Domain\Subscription\Model\SubscribePageData; use PhpList\Core\Domain\Subscription\Repository\SubscriberPageDataRepository; @@ -20,7 +21,7 @@ public function __construct( ) { } - public function createPage(string $title, bool $active = false, ?int $owner = null): SubscribePage + public function createPage(string $title, bool $active = false, ?Administrator $owner = null): SubscribePage { $page = new SubscribePage(); $page->setTitle($title) @@ -43,8 +44,12 @@ public function getPage(int $id): SubscribePage return $page; } - public function updatePage(SubscribePage $page, ?string $title = null, ?bool $active = null, ?int $owner = null): SubscribePage - { + public function updatePage( + SubscribePage $page, + ?string $title = null, + ?bool $active = null, + ?Administrator $owner = null + ): SubscribePage { if ($title !== null) { $page->setTitle($title); } @@ -74,14 +79,14 @@ public function deletePage(SubscribePage $page): void public function getPageData(SubscribePage $page, string $name): ?string { /** @var SubscribePageData|null $data */ - $data = $this->pageDataRepository->findOneBy(['id' => $page->getId(), 'name' => $name]); + $data = $this->pageDataRepository->findByPageAndName($page, $name); return $data?->getData(); } public function setPageData(SubscribePage $page, string $name, ?string $value): SubscribePageData { /** @var SubscribePageData|null $data */ - $data = $this->pageDataRepository->findOneBy(['id' => $page->getId(), 'name' => $name]); + $data = $this->pageDataRepository->findByPageAndName($page, $name); if (!$data) { $data = (new SubscribePageData()) From 1a75cd4ef71a1ff2691d202dfb38437a4b4290ee Mon Sep 17 00:00:00 2001 From: Tatevik Date: Tue, 12 Aug 2025 21:03:57 +0400 Subject: [PATCH 3/5] test --- .../Manager/SubscribePageManagerTest.php | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php 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..1f9bfb42 --- /dev/null +++ b/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php @@ -0,0 +1,233 @@ +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('findByPageAndName') + ->with($page, 'key') + ->willReturn($data); + + $result = $this->manager->getPageData($page, 'key'); + $this->assertSame('value', $result); + } + + public function testGetPageDataReturnsNullWhenNotFound(): void + { + $page = new SubscribePage(); + + $this->pageDataRepository + ->expects($this->once()) + ->method('findByPageAndName') + ->with($page, 'missing') + ->willReturn(null); + + $result = $this->manager->getPageData($page, 'missing'); + $this->assertNull($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()); + } +} From e70d14027704e0f89a76731686aa6190e49c68da Mon Sep 17 00:00:00 2001 From: Tatevik Date: Tue, 12 Aug 2025 21:05:55 +0400 Subject: [PATCH 4/5] ci fix --- .../Subscription/Service/Manager/SubscribePageManager.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Domain/Subscription/Service/Manager/SubscribePageManager.php b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php index a21050eb..76539574 100644 --- a/src/Domain/Subscription/Service/Manager/SubscribePageManager.php +++ b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php @@ -21,6 +21,9 @@ public function __construct( ) { } + /** + * @SuppressWarnings("BooleanArgumentFlag") + */ public function createPage(string $title, bool $active = false, ?Administrator $owner = null): SubscribePage { $page = new SubscribePage(); From 11181e64c3bf7f34af1f49a221fd6077c9e753dc Mon Sep 17 00:00:00 2001 From: Tatevik Date: Wed, 13 Aug 2025 13:52:04 +0400 Subject: [PATCH 5/5] getByPage data --- .../SubscriberPageDataRepository.php | 6 ++++++ .../Repository/SubscriberPageRepository.php | 16 ++++++++++++++ .../Service/Manager/SubscribePageManager.php | 7 +++---- .../Manager/SubscribePageManagerTest.php | 21 ++++++++++--------- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php b/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php index 504174a6..68d0d6bc 100644 --- a/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php +++ b/src/Domain/Subscription/Repository/SubscriberPageDataRepository.php @@ -18,4 +18,10 @@ public function findByPageAndName(SubscribePage $page, string $name): ?Subscribe { 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 index 76539574..8e429dc4 100644 --- a/src/Domain/Subscription/Service/Manager/SubscribePageManager.php +++ b/src/Domain/Subscription/Service/Manager/SubscribePageManager.php @@ -79,11 +79,10 @@ public function deletePage(SubscribePage $page): void $this->pageRepository->remove($page); } - public function getPageData(SubscribePage $page, string $name): ?string + /** @return SubscribePageData[] */ + public function getPageData(SubscribePage $page): array { - /** @var SubscribePageData|null $data */ - $data = $this->pageDataRepository->findByPageAndName($page, $name); - return $data?->getData(); + return $this->pageDataRepository->getByPage($page,); } public function setPageData(SubscribePage $page, string $name, ?string $value): SubscribePageData diff --git a/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php b/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php index 1f9bfb42..422c78a7 100644 --- a/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php +++ b/tests/Unit/Domain/Subscription/Service/Manager/SubscribePageManagerTest.php @@ -153,12 +153,13 @@ public function testGetPageDataReturnsStringWhenFound(): void $this->pageDataRepository ->expects($this->once()) - ->method('findByPageAndName') - ->with($page, 'key') - ->willReturn($data); + ->method('getByPage') + ->with($page) + ->willReturn([$data]); - $result = $this->manager->getPageData($page, 'key'); - $this->assertSame('value', $result); + $result = $this->manager->getPageData($page); + $this->assertIsArray($result); + $this->assertSame('value', $result[0]->getData()); } public function testGetPageDataReturnsNullWhenNotFound(): void @@ -167,12 +168,12 @@ public function testGetPageDataReturnsNullWhenNotFound(): void $this->pageDataRepository ->expects($this->once()) - ->method('findByPageAndName') - ->with($page, 'missing') - ->willReturn(null); + ->method('getByPage') + ->with($page) + ->willReturn([]); - $result = $this->manager->getPageData($page, 'missing'); - $this->assertNull($result); + $result = $this->manager->getPageData($page); + $this->assertEmpty($result); } public function testSetPageDataUpdatesExistingDataAndFlushes(): void