refactor(manga): Chapter entité DDD de Manga + AggregateRoot

- Ajoute AggregateRoot dans Shared (domain events + pull pattern)
- Manga extends AggregateRoot, devient vrai aggregate root DDD
- Chapter passe de readonly à entité mutable avec MangaId VO
- Manga expose les méthodes domaine pour toute mutation de chapitre :
  addChapter, updateChapterTitle/Volume/Pages, hideChapter, removeChapterPages
- Supprime saveChapter/updateChapter/deleteChapter de MangaRepositoryInterface
- save(Manga) gère désormais la persistance des chapitres via pull pattern
- Tous les handlers/listeners passent par l'agrégat (plus d'accès direct)
- phparkitect autorise AggregateRoot dans les couches Domain

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-09 19:15:11 +01:00
parent a4b3d8a5f1
commit 2c051351a8
18 changed files with 226 additions and 255 deletions

View File

@@ -115,6 +115,44 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
if ($entity->getId()) {
$manga->updateId(new MangaId((string) $entity->getId()));
}
// Handle new chapters added through the aggregate
foreach ($manga->pullNewChapters() as $chapter) {
$mangaEntity = $this->entityManager->find(EntityManga::class, (int) $manga->getId()->getValue());
$chapterEntity = new EntityChapter();
$chapterEntity->setManga($mangaEntity)
->setNumber($chapter->getNumber())
->setTitle($chapter->getTitle())
->setVolume($chapter->getVolume())
->setVisible($chapter->isVisible())
->setPagesDirectory($chapter->getPagesDirectory())
->setPageCount($chapter->getPageCount());
$this->entityManager->persist($chapterEntity);
}
// Handle chapters modified through the aggregate
foreach ($manga->pullModifiedChapters() as $chapter) {
$chapterEntity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
if ($chapterEntity) {
$chapterEntity->setVisible($chapter->isVisible())
->setPagesDirectory($chapter->getPagesDirectory())
->setPageCount($chapter->getPageCount())
->setTitle($chapter->getTitle())
->setVolume($chapter->getVolume())
->setCbzPath($chapter->getPagesDirectory());
$this->entityManager->persist($chapterEntity);
}
}
// Handle chapters deleted through the aggregate
foreach ($manga->pullChaptersToDelete() as $chapter) {
$chapterEntity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
if ($chapterEntity) {
$this->entityManager->remove($chapterEntity);
}
}
$this->entityManager->flush();
}
public function delete(DomainManga $manga): void
@@ -166,29 +204,6 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
return $entity ? $this->toDomain($entity) : null;
}
public function saveChapter(Chapter $chapter): ChapterId
{
$manga = $this->entityManager->find(EntityManga::class, $chapter->getMangaId());
if (!$manga) {
throw new \RuntimeException('Manga not found');
}
$entity = new EntityChapter();
$entity->setManga($manga)
->setNumber($chapter->getNumber())
->setTitle($chapter->getTitle())
->setVolume($chapter->getVolume())
->setVisible($chapter->isVisible())
->setPagesDirectory($chapter->getPagesDirectory())
->setPageCount($chapter->getPageCount());
$this->entityManager->persist($entity);
$this->entityManager->flush();
return new ChapterId((string) $entity->getId());
}
public function findChapterById(string $id): ?Chapter
{
$entity = $this->entityManager->find(EntityChapter::class, $id);
@@ -226,36 +241,6 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
return $entity ? $this->toChapterDomain($entity) : null;
}
public function updateChapter(Chapter $chapter): void
{
$entity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
if (!$entity) {
throw new \RuntimeException(sprintf('Chapter with id %s not found', $chapter->getId()));
}
$entity->setVisible($chapter->isVisible())
->setPagesDirectory($chapter->getPagesDirectory())
->setPageCount($chapter->getPageCount())
->setTitle($chapter->getTitle())
->setVolume($chapter->getVolume())
// Keep cbzPath in sync during transition (Phase 4 will drop this column)
->setCbzPath($chapter->getPagesDirectory());
$this->entityManager->persist($entity);
$this->entityManager->flush();
}
public function deleteChapter(Chapter $chapter): void
{
$entity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
if ($entity) {
$this->entityManager->remove($entity);
$this->entityManager->flush();
}
}
public function findChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
{
$entities = $this->entityManager->getRepository(EntityChapter::class)
@@ -417,7 +402,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface
{
return new Chapter(
id: new ChapterId((string) $entity->getId()),
mangaId: (string) $entity->getManga()->getId(),
mangaId: new MangaId((string) $entity->getManga()->getId()),
number: $entity->getNumber(),
title: $entity->getTitle(),
volume: $entity->getVolume(),