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

@@ -8,6 +8,7 @@ use App\Domain\Manga\Domain\Model\Chapter;
use App\Domain\Manga\Domain\Model\Manga;
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
class InMemoryMangaRepository implements MangaRepositoryInterface
@@ -21,9 +22,6 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
/** @var array<string, Chapter> */
private array $chaptersById = [];
/** @var array<Chapter> */
private array $savedChapters = [];
public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
{
$sortedMangas = array_values($this->mangas);
@@ -65,6 +63,39 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
public function save(Manga $manga): void
{
$this->mangas[$manga->getId()->getValue()] = $manga;
foreach ($manga->pullNewChapters() as $chapter) {
$mangaIdValue = $chapter->getMangaId()->getValue();
if (!isset($this->chapters[$mangaIdValue])) {
$this->chapters[$mangaIdValue] = [];
}
$this->chapters[$mangaIdValue][] = $chapter;
$this->chaptersById[$chapter->getId()] = $chapter;
}
foreach ($manga->pullModifiedChapters() as $chapter) {
$this->chaptersById[$chapter->getId()] = $chapter;
$mangaIdValue = $chapter->getMangaId()->getValue();
if (isset($this->chapters[$mangaIdValue])) {
foreach ($this->chapters[$mangaIdValue] as $key => $existing) {
if ($existing->getId() === $chapter->getId()) {
$this->chapters[$mangaIdValue][$key] = $chapter;
break;
}
}
}
}
foreach ($manga->pullChaptersToDelete() as $chapter) {
unset($this->chaptersById[$chapter->getId()]);
$mangaIdValue = $chapter->getMangaId()->getValue();
if (isset($this->chapters[$mangaIdValue])) {
$this->chapters[$mangaIdValue] = array_values(array_filter(
$this->chapters[$mangaIdValue],
fn (Chapter $c) => $c->getId() !== $chapter->getId()
));
}
}
}
public function delete(Manga $manga): void
@@ -121,57 +152,18 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter
{
foreach ($this->chaptersById as $chapter) {
if ($chapter->getMangaId() === $mangaId && $chapter->getNumber() === $chapterNumber) {
if ($chapter->getMangaId()->getValue() === $mangaId && $chapter->getNumber() === $chapterNumber) {
return $chapter;
}
}
return null;
}
public function saveChapter(Chapter $chapter): ChapterId
{
$this->savedChapters[] = $chapter;
if (!isset($this->chapters[$chapter->getMangaId()])) {
$this->chapters[$chapter->getMangaId()] = [];
}
$this->chapters[$chapter->getMangaId()][] = $chapter;
$this->chaptersById[$chapter->getId()] = $chapter;
return new ChapterId($chapter->getId());
}
public function updateChapter(Chapter $chapter): void
{
$this->chaptersById[$chapter->getId()] = $chapter;
if (isset($this->chapters[$chapter->getMangaId()])) {
foreach ($this->chapters[$chapter->getMangaId()] as $key => $existing) {
if ($existing->getId() === $chapter->getId()) {
$this->chapters[$chapter->getMangaId()][$key] = $chapter;
return;
}
}
}
}
public function deleteChapter(Chapter $chapter): void
{
unset($this->chaptersById[$chapter->getId()]);
if (isset($this->chapters[$chapter->getMangaId()])) {
$this->chapters[$chapter->getMangaId()] = array_values(
array_filter(
$this->chapters[$chapter->getMangaId()],
fn (Chapter $c) => $c->getId() !== $chapter->getId()
)
);
}
}
public function findChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
{
return array_values(array_filter(
$this->chaptersById,
fn (Chapter $chapter) => $chapter->getMangaId() === $mangaId && $chapter->getVolume() === $volume
fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId && $chapter->getVolume() === $volume
));
}
@@ -180,7 +172,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return array_values(array_filter(
$this->chaptersById,
fn (Chapter $chapter) =>
$chapter->getMangaId() === $mangaId &&
$chapter->getMangaId()->getValue() === $mangaId &&
$chapter->getVolume() === $volume &&
$chapter->isVisible()
));
@@ -191,7 +183,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return array_values(array_filter(
$this->chaptersById,
fn (Chapter $chapter) =>
$chapter->getMangaId() === $mangaId &&
$chapter->getMangaId()->getValue() === $mangaId &&
$chapter->getVolume() === $volume &&
$chapter->isVisible() &&
$chapter->isAvailable()
@@ -205,7 +197,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
for ($i = 1; $i <= $count; $i++) {
$chapter = new Chapter(
id: new ChapterId((string)$i),
mangaId: $mangaId,
mangaId: new MangaId($mangaId),
number: (float)$i,
title: "Chapter $i",
volume: (int)ceil($i / 10),
@@ -227,18 +219,11 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return null;
}
/** @return array<Chapter> */
public function getSavedChapters(): array
{
return $this->savedChapters;
}
public function clear(): void
{
$this->mangas = [];
$this->chapters = [];
$this->chaptersById = [];
$this->savedChapters = [];
}
public function search(string $query, int $page = 1, int $limit = 20): array