mangaRepository->findById($query->mangaId); if (!$manga) { throw new MangaNotFoundException(); } $allChapters = $this->mangaRepository->findAllChapters( mangaId: $query->mangaId, sortOrder: 'asc' ); $grouped = $this->groupChapters($allChapters); if ('desc' === $query->sortOrder) { usort($grouped, fn (ChapterResponse $a, ChapterResponse $b) => $b->number <=> $a->number); } $total = count($grouped); $offset = ($query->page - 1) * $query->limit; $paginatedChapters = array_slice($grouped, $offset, $query->limit); return new ChapterListResponse( chapters: $paginatedChapters, total: $total, page: $query->page, limit: $query->limit ); } /** @param Chapter[] $chapters */ private function groupChapters(array $chapters): array { $result = []; $currentGroup = []; $currentPagesDir = null; $currentVolume = null; foreach ($chapters as $chapter) { $pagesDir = $chapter->getPagesDirectory(); $volume = $chapter->getVolume(); if (null !== $pagesDir && null !== $volume) { if ($pagesDir === $currentPagesDir && $volume === $currentVolume) { $currentGroup[] = $chapter; } else { if (!empty($currentGroup)) { $result[] = $this->buildVolumeGroupResponse($currentGroup); } $currentGroup = [$chapter]; $currentPagesDir = $pagesDir; $currentVolume = $volume; } } else { if (!empty($currentGroup)) { $result[] = $this->buildVolumeGroupResponse($currentGroup); $currentGroup = []; $currentPagesDir = null; $currentVolume = null; } $result[] = new ChapterResponse( id: $chapter->getId(), number: $chapter->getNumber(), title: $chapter->getTitle(), volume: $chapter->getVolume(), isVisible: $chapter->isVisible(), pagesDirectory: $chapter->getPagesDirectory(), createdAt: $chapter->getCreatedAt()->format(\DateTimeInterface::RFC3339) ); } } if (!empty($currentGroup)) { $result[] = $this->buildVolumeGroupResponse($currentGroup); } return $result; } /** @param Chapter[] $group */ private function buildVolumeGroupResponse(array $group): ChapterResponse { $first = $group[0]; $numbers = array_map(fn (Chapter $c) => $c->getNumber(), $group); $min = min($numbers); $max = max($numbers); $fmt = fn (float $n) => $n == (int) $n ? (string) (int) $n : (string) $n; $range = count($group) > 1 ? $fmt($min).'-'.$fmt($max) : $fmt($min); return new ChapterResponse( id: $first->getId(), number: $first->getNumber(), title: $first->getTitle(), volume: $first->getVolume(), isVisible: $first->isVisible(), pagesDirectory: $first->getPagesDirectory(), createdAt: $first->getCreatedAt()->format(\DateTimeInterface::RFC3339), isVolumeGroup: true, volumeChaptersRange: $range, volumeChapterCount: count($group) ); } }