getExternalId()) { throw new \RuntimeException('Manga has no external ID'); } $externalId = $manga->getExternalId()->getValue(); $offset = 0; $limit = 500; $hasMore = true; $chaptersByNumber = []; $chapterLanguages = []; // Pour stocker la langue de chaque chapitre $chapterNumbers = []; while ($hasMore) { $feed = $this->mangadxClient->getMangaFeed( $externalId, $offset, $limit ); foreach ($feed['data'] as $chapterData) { $chapterNumber = (float) $chapterData['attributes']['chapter']; $language = $chapterData['attributes']['translatedLanguage']; $title = $chapterData['attributes']['title']; // Pour les langues autres que français et anglais, on utilise un titre générique if (!in_array($language, ['fr', 'en'])) { $title = "Chapter {$chapterNumber}"; } // Définir les règles de priorité des langues (fr > en > autres) $shouldReplaceChapter = false; if (!isset($chaptersByNumber[(string) $chapterNumber])) { // Si c'est le premier chapitre avec ce numéro qu'on rencontre $shouldReplaceChapter = true; $chapterNumbers[] = $chapterNumber; } elseif ('fr' === $language) { // Le français est toujours prioritaire $shouldReplaceChapter = true; } elseif ('en' === $language && 'fr' !== $chapterLanguages[(string) $chapterNumber]) { // L'anglais est prioritaire sur les autres langues, sauf le français $shouldReplaceChapter = true; } if ($shouldReplaceChapter) { $chaptersByNumber[(string) $chapterNumber] = new Chapter( new ChapterId((string) Uuid::uuid4()), $manga->getId(), $chapterNumber, $title, isset($chapterData['attributes']['volume']) ? (int) $chapterData['attributes']['volume'] : null, true, null, 0, new \DateTimeImmutable() ); $chapterLanguages[(string) $chapterNumber] = $language; } } $offset += $limit; $hasMore = count($feed['data']) === $limit; } // Harmonisation des volumes: si le chapitre précédent et suivant ont un volume null, alors le chapitre actuel aussi $this->harmonizeVolumes($chaptersByNumber); // Récupère les chapitres existants $existingChapters = $this->mangaRepository->findExistingChaptersByNumbers( $manga->getId()->getValue(), $chapterNumbers ); $newChapterIds = []; // Sauvegarde uniquement les nouveaux chapitres et collecte leurs numéros foreach ($chaptersByNumber as $chapterNumber => $chapter) { if (!isset($existingChapters[(float) $chapterNumber])) { $manga->addChapter($chapter); $newChapterIds[] = $chapter->getNumber(); } } return $newChapterIds; } /** * Harmonise les volumes des chapitres: * - Si le chapitre précédent et suivant ont un volume null, alors le chapitre actuel aussi * - Si le chapitre précédent et suivant ont le même volume, alors le chapitre actuel aura ce volume * - Remplit les "trous" de volumes manquants dans une séquence */ private function harmonizeVolumes(array &$chaptersByNumber): void { // Trie les chapitres par numéro pour faciliter la recherche des adjacents uksort($chaptersByNumber, fn ($a, $b) => (float) $a <=> (float) $b); $chapterNumbers = array_keys($chaptersByNumber); $count = count($chapterNumbers); // Première passe : harmonisation locale (chapitres adjacents) for ($i = 1; $i < $count - 1; ++$i) { $prevChapterNum = $chapterNumbers[$i - 1]; $currentChapterNum = $chapterNumbers[$i]; $nextChapterNum = $chapterNumbers[$i + 1]; $prevChapter = $chaptersByNumber[$prevChapterNum]; $currentChapter = $chaptersByNumber[$currentChapterNum]; $nextChapter = $chaptersByNumber[$nextChapterNum]; $prevVolume = $prevChapter->getVolume(); $currentVolume = $currentChapter->getVolume(); $nextVolume = $nextChapter->getVolume(); // Règle 1: Si précédent et suivant sont null, alors actuel aussi if (null === $prevVolume && null === $nextVolume && null !== $currentVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), $currentChapter->getNumber(), $currentChapter->getTitle(), null, // volume = null $currentChapter->isVisible(), $currentChapter->getPagesDirectory(), $currentChapter->getPageCount(), $currentChapter->getCreatedAt() ); } // Règle 2: Si précédent et suivant ont le même volume, alors actuel aussi elseif (null !== $prevVolume && $prevVolume === $nextVolume && $currentVolume !== $prevVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), $currentChapter->getNumber(), $currentChapter->getTitle(), $prevVolume, // prend le volume des adjacents $currentChapter->isVisible(), $currentChapter->getPagesDirectory(), $currentChapter->getPageCount(), $currentChapter->getCreatedAt() ); } } // Deuxième passe : comblement des trous de volumes $this->fillVolumeGaps($chaptersByNumber, $chapterNumbers); } /** * Remplit les "trous" de volumes manquants dans une séquence. */ private function fillVolumeGaps(array &$chaptersByNumber, array $chapterNumbers): void { $count = count($chapterNumbers); for ($i = 0; $i < $count; ++$i) { $currentChapterNum = $chapterNumbers[$i]; $currentChapter = $chaptersByNumber[$currentChapterNum]; if (null !== $currentChapter->getVolume()) { continue; // Ce chapitre a déjà un volume } // Cherche le volume précédent non-null $prevVolume = null; for ($j = $i - 1; $j >= 0; --$j) { $prevChapter = $chaptersByNumber[$chapterNumbers[$j]]; if (null !== $prevChapter->getVolume()) { $prevVolume = $prevChapter->getVolume(); break; } } // Cherche le volume suivant non-null $nextVolume = null; for ($k = $i + 1; $k < $count; ++$k) { $nextChapter = $chaptersByNumber[$chapterNumbers[$k]]; if (null !== $nextChapter->getVolume()) { $nextVolume = $nextChapter->getVolume(); break; } } // Priorité au volume précédent : le chapitre appartient à la fin du volume en cours // Couvre les cas : milieu de volume (prev=next), transition entre deux volumes (prev≠next) if (null !== $prevVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), $currentChapter->getNumber(), $currentChapter->getTitle(), $prevVolume, $currentChapter->isVisible(), $currentChapter->getPagesDirectory(), $currentChapter->getPageCount(), $currentChapter->getCreatedAt() ); } // Sinon utilise le volume suivant (chapitres en début de série) elseif (null !== $nextVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), $currentChapter->getNumber(), $currentChapter->getTitle(), $nextVolume, $currentChapter->isVisible(), $currentChapter->getPagesDirectory(), $currentChapter->getPageCount(), $currentChapter->getCreatedAt() ); } } } }