*/ private array $mangas = []; /** @var array> */ private array $chapters = []; /** @var array */ private array $chaptersById = []; public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array { $sortedMangas = array_values($this->mangas); usort($sortedMangas, function (Manga $a, Manga $b) use ($sortBy, $sortOrder) { $valueA = $this->getPropertyValue($a, $sortBy); $valueB = $this->getPropertyValue($b, $sortBy); return $sortOrder === 'asc' ? $valueA <=> $valueB : $valueB <=> $valueA; }); $offset = ($page - 1) * $limit; return array_slice($sortedMangas, $offset, $limit); } public function count(): int { return count($this->mangas); } public function findById(string $id): ?Manga { return $this->mangas[$id] ?? null; } public function findBySlug(MangaSlug $slug): ?Manga { foreach ($this->mangas as $manga) { if ($manga->getSlug()->getValue() === $slug->getValue()) { return $manga; } } return null; } 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 { unset($this->mangas[$manga->getId()->getValue()]); } private function getPropertyValue(Manga $manga, string $property): mixed { return match($property) { 'title' => $manga->getTitle()->getValue(), 'publicationYear' => $manga->getPublicationYear(), default => throw new \InvalidArgumentException("Unknown sort property: $property") }; } public function findChapters(string $mangaId, int $page = 1, int $limit = 20, string $sortOrder = 'desc'): array { if (!isset($this->chapters[$mangaId])) { return []; } $chapters = $this->chapters[$mangaId]; usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) { return $sortOrder === 'desc' ? $b->getNumber() <=> $a->getNumber() : $a->getNumber() <=> $b->getNumber(); }); $offset = ($page - 1) * $limit; return array_slice($chapters, $offset, $limit); } public function countChapters(string $mangaId): int { return count($this->chapters[$mangaId] ?? []); } public function countAvailableChapters(string $mangaId): int { return count(array_filter( $this->chapters[$mangaId] ?? [], fn (Chapter $c) => $c->isAvailable() )); } public function findChapterById(string $id): ?Chapter { return $this->chaptersById[$id] ?? null; } public function findVisibleChapterById(string $id): ?Chapter { $chapter = $this->chaptersById[$id] ?? null; if ($chapter && $chapter->isVisible()) { return $chapter; } return null; } public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter { foreach ($this->chaptersById as $chapter) { if ($chapter->getMangaId()->getValue() === $mangaId && $chapter->getNumber() === $chapterNumber) { return $chapter; } } return null; } public function findChaptersByMangaIdAndVolume(string $mangaId, int $volume): array { return array_values(array_filter( $this->chaptersById, fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId && $chapter->getVolume() === $volume )); } public function findVisibleChaptersByMangaIdAndVolume(string $mangaId, int $volume): array { return array_values(array_filter( $this->chaptersById, fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId && $chapter->getVolume() === $volume && $chapter->isVisible() )); } public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array { return array_values(array_filter( $this->chaptersById, fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId && $chapter->getVolume() === $volume && $chapter->isVisible() && $chapter->isAvailable() )); } public function addChaptersToManga(string $mangaId, int $count): void { $this->chapters[$mangaId] = []; for ($i = 1; $i <= $count; $i++) { $chapter = new Chapter( id: new ChapterId((string)$i), mangaId: new MangaId($mangaId), number: (float)$i, title: "Chapter $i", volume: (int)ceil($i / 10), isVisible: true, createdAt: new \DateTimeImmutable() ); $this->chapters[$mangaId][] = $chapter; $this->chaptersById[$chapter->getId()] = $chapter; } } public function findByExternalId(ExternalId $externalId): ?Manga { foreach ($this->mangas as $manga) { if ($manga->getExternalId() && $manga->getExternalId()->getValue() === $externalId->getValue()) { return $manga; } } return null; } public function clear(): void { $this->mangas = []; $this->chapters = []; $this->chaptersById = []; } public function search(string $query, int $page = 1, int $limit = 20): array { $filteredMangas = array_filter($this->mangas, function (Manga $manga) use ($query) { $searchableFields = [ $manga->getTitle()->getValue(), $manga->getSlug()->getValue(), $manga->getAuthor(), $manga->getDescription() ]; foreach ($manga->getAlternativeSlugs() as $altSlug) { $searchableFields[] = $altSlug; } foreach ($searchableFields as $field) { if (str_contains(strtolower($field), strtolower($query))) { return true; } } return false; }); $sortedMangas = array_values($filteredMangas); usort($sortedMangas, fn (Manga $a, Manga $b) => $a->getTitle()->getValue() <=> $b->getTitle()->getValue()); $offset = ($page - 1) * $limit; return array_slice($sortedMangas, $offset, $limit); } public function countSearch(string $query): int { return count($this->search($query, 1, PHP_INT_MAX)); } public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array { if (!isset($this->chapters[$mangaId])) { return []; } $result = []; foreach ($this->chapters[$mangaId] as $chapter) { if (in_array($chapter->getNumber(), $chapterNumbers)) { $result[$chapter->getNumber()] = $chapter; } } return $result; } public function findByMonitoringCriteria(MonitoringCriteria $criteria): array { return array_filter( array_values($this->mangas), function (Manga $manga) use ($criteria) { if ($manga->getMonitoringStatus()->isEnabled() !== $criteria->enabled) { return false; } if ($criteria->lastCheckBefore !== null) { $lastCheck = $manga->getLastMonitoringCheck(); if ($lastCheck === null || $lastCheck >= $criteria->lastCheckBefore) { return false; } } return true; } ); } }