122 lines
4.0 KiB
PHP
122 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\Manga\Application\QueryHandler;
|
|
|
|
use App\Domain\Manga\Application\Query\FindMangaMatchByFilename;
|
|
use App\Domain\Manga\Application\Response\MangaMatchItem;
|
|
use App\Domain\Manga\Application\Response\MangaMatchResponse;
|
|
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
|
|
use App\Domain\Manga\Domain\Contract\Service\FilenameAnalyzerInterface;
|
|
use App\Domain\Manga\Domain\Model\Manga;
|
|
|
|
readonly class FindMangaMatchByFilenameHandler
|
|
{
|
|
public function __construct(
|
|
private FilenameAnalyzerInterface $filenameAnalyzer,
|
|
private MangaRepositoryInterface $mangaRepository
|
|
) {
|
|
}
|
|
|
|
public function handle(FindMangaMatchByFilename $query): MangaMatchResponse
|
|
{
|
|
// Analyser le nom de fichier pour extraire les informations
|
|
$analyzedFilename = $this->filenameAnalyzer->analyze($query->filename);
|
|
|
|
$searchedTitle = $analyzedFilename->getTitle()->getValue();
|
|
$chapterNumber = $analyzedFilename->hasChapterNumber()
|
|
? $analyzedFilename->getChapterNumber()->getValue()
|
|
: null;
|
|
$volumeNumber = $analyzedFilename->hasVolumeNumber()
|
|
? $analyzedFilename->getVolumeNumber()->getValue()
|
|
: null;
|
|
|
|
// Rechercher les mangas correspondants
|
|
$foundMangas = $this->mangaRepository->search($searchedTitle, 1, 10);
|
|
$matches = [];
|
|
|
|
foreach ($foundMangas as $manga) {
|
|
$mangaId = $manga->getId()->getValue();
|
|
|
|
// Calculer un score de correspondance
|
|
$matchScore = $this->calculateMatchScore(
|
|
$manga,
|
|
$searchedTitle
|
|
);
|
|
|
|
$matches[] = new MangaMatchItem(
|
|
id: $mangaId,
|
|
title: $manga->getTitle()->getValue(),
|
|
slug: $manga->getSlug()->getValue(),
|
|
alternativeSlugs: $manga->getAlternativeSlugs(),
|
|
thumbnailUrl: $manga->getImageUrls()->getThumbnail(),
|
|
matchScore: $matchScore,
|
|
chapterNumber: $chapterNumber,
|
|
volumeNumber: $volumeNumber
|
|
);
|
|
}
|
|
|
|
// Trier les résultats par score de correspondance (du plus élevé au plus faible)
|
|
usort($matches, fn ($a, $b) => $b->matchScore <=> $a->matchScore);
|
|
|
|
return new MangaMatchResponse(
|
|
matches: $matches,
|
|
chapterNumber: $chapterNumber,
|
|
volumeNumber: $volumeNumber,
|
|
possibleTitles: [$searchedTitle]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calcule un score de correspondance entre le manga et le titre recherché
|
|
* Score plus élevé = meilleure correspondance
|
|
*/
|
|
private function calculateMatchScore(Manga $manga, string $searchedTitle): int
|
|
{
|
|
$score = 0;
|
|
$mangaTitle = $manga->getTitle()->getValue();
|
|
$mangaSlug = $manga->getSlug()->getValue();
|
|
|
|
// Correspondance exacte avec le titre
|
|
if (strtolower($mangaTitle) === strtolower($searchedTitle)) {
|
|
$score += 100;
|
|
}
|
|
|
|
// Correspondance exacte avec le slug
|
|
if (strtolower($mangaSlug) === strtolower($searchedTitle)) {
|
|
$score += 90;
|
|
}
|
|
|
|
// Correspondance avec les slugs alternatifs
|
|
foreach ($manga->getAlternativeSlugs() as $altSlug) {
|
|
if (strtolower($altSlug) === strtolower($searchedTitle)) {
|
|
$score += 80;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Le titre du manga contient le terme recherché
|
|
if (stripos($mangaTitle, $searchedTitle) !== false) {
|
|
$score += 50;
|
|
}
|
|
|
|
// Le terme recherché contient le titre du manga
|
|
if (stripos($searchedTitle, $mangaTitle) !== false) {
|
|
$score += 40;
|
|
}
|
|
|
|
// Similarité de Levenshtein (pour les fautes de frappe)
|
|
$levenshteinDistance = levenshtein(
|
|
strtolower($mangaTitle),
|
|
strtolower($searchedTitle)
|
|
);
|
|
|
|
if ($levenshteinDistance <= 3) {
|
|
$score += (3 - $levenshteinDistance) * 10;
|
|
}
|
|
|
|
return $score;
|
|
}
|
|
}
|