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:
parent
a4b3d8a5f1
commit
2c051351a8
@@ -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
|
||||
|
||||
@@ -76,19 +76,18 @@ class ImportChapterHandlerTest extends TestCase
|
||||
['action', 'adventure'],
|
||||
'ongoing'
|
||||
);
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
// Create an existing chapter without pages
|
||||
// Create an existing chapter without pages and add through the aggregate
|
||||
$existingChapter = new Chapter(
|
||||
new ChapterId('chapter-123'),
|
||||
$mangaId,
|
||||
new MangaId($mangaId),
|
||||
1.5,
|
||||
'Chapter 1.5',
|
||||
1,
|
||||
true,
|
||||
null
|
||||
);
|
||||
$this->mangaRepository->saveChapter($existingChapter);
|
||||
$manga->addChapter($existingChapter);
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
// Import the same chapter with CBZ
|
||||
$cbzBinary = $this->createValidCbzBinary();
|
||||
@@ -105,7 +104,7 @@ class ImportChapterHandlerTest extends TestCase
|
||||
$updatedChapter = $this->mangaRepository->findChapterById('chapter-123');
|
||||
$this->assertNotNull($updatedChapter);
|
||||
$this->assertEquals('chapter-123', $updatedChapter->getId());
|
||||
$this->assertEquals($mangaId, $updatedChapter->getMangaId());
|
||||
$this->assertEquals($mangaId, $updatedChapter->getMangaId()->getValue());
|
||||
$this->assertEquals(1.5, $updatedChapter->getNumber());
|
||||
$this->assertEquals('Chapter 1.5', $updatedChapter->getTitle());
|
||||
$this->assertEquals(1, $updatedChapter->getVolume());
|
||||
|
||||
@@ -46,21 +46,20 @@ class ImportVolumeHandlerTest extends TestCase
|
||||
['action', 'adventure'],
|
||||
'ongoing'
|
||||
);
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
// Create chapters in volume 1
|
||||
// Create chapters in volume 1 and add through the aggregate
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$chapter = new Chapter(
|
||||
new ChapterId("chapter-$i"),
|
||||
$mangaId,
|
||||
new MangaId($mangaId),
|
||||
(float)$i,
|
||||
"Chapter $i",
|
||||
$volumeNumber,
|
||||
true,
|
||||
null
|
||||
);
|
||||
$this->mangaRepository->saveChapter($chapter);
|
||||
$manga->addChapter($chapter);
|
||||
}
|
||||
$this->mangaRepository->save($manga);
|
||||
|
||||
$cbzBinary = $this->createValidCbzBinary();
|
||||
$command = new ImportVolume(
|
||||
|
||||
Reference in New Issue
Block a user