feat: migrer vers Symfony 8, PHP 8.4 et les dépendances majeures associées

- PHP 8.3 → 8.4 (Dockerfile + composer.json)
- Symfony 7.0 → 8.0 (tous les composants symfony/*)
- API Platform 3.x → 4.x : migration openapiContext → openapi: new Operation(...)
- Doctrine DBAL 3 → 4 : suppression use_savepoints, replace prepare/executeQuery
- Doctrine ORM 2.x → 3.x : ClassMetadataInfo → ClassMetadata, setParameters → setParameter
- Doctrine Bundle 2.x → 3.x, Fixtures Bundle 3.x → 4.x
- zenstruck/foundry 1.x → 2.x : ModelFactory → PersistentObjectFactory, getDefaults → defaults
- phpmd/phpmd 2.x → 3.x-dev (seule version supportant Symfony 8)
- phparkitect 0.3 → 0.8 : NotDependsOnTheseNamespaces prend un array
- symfony/mercure-bundle 0.3 → 0.4, symfony/monolog-bundle 3 → 4
- Suppression de runtime/frankenphp-symfony (intégré nativement dans symfony/runtime 8)
- worker.Caddyfile : suppression de APP_RUNTIME (détection automatique Symfony 8)
- Routes errors.xml/wdt.xml/profiler.xml → .php (Symfony 8 supprime le XML)
- Types::ARRAY → Types::JSON dans Entity/Manga.php (DBAL 4 retire array type)
- Suppression de src/Schedule.php (doublon vide avec MonitoringSchedule)
- Tests : hydra:Collection → Collection, hydra:member → member (API Platform 4)
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-26 17:55:12 +01:00
parent 5a0888eb28
commit 5ed303612a
371 changed files with 6194 additions and 4160 deletions

View File

@@ -15,7 +15,7 @@ class InMemoryChapterSynchronizationService implements ChapterSynchronizationSer
$this->synchronizedChapters[$manga->getId()->getValue()] = [
'manga_id' => $manga->getId()->getValue(),
'external_id' => $manga->getExternalId()?->getValue(),
'synchronized_at' => new \DateTimeImmutable()
'synchronized_at' => new \DateTimeImmutable(),
];
// Retourne les IDs des chapitres synchronisés (simulation)

View File

@@ -11,19 +11,21 @@ class InMemoryImageProcessor implements ImageProcessorInterface
/** @var array<string, string> */
private array $downloadedImages = [];
/** @var array<string, string> */
private array $thumbnails = [];
public function downloadImage(string $imageUrl): string
{
$this->downloadedImages[$imageUrl] = self::FAKE_FULL_IMAGE_PATH;
return self::FAKE_FULL_IMAGE_PATH;
}
public function createThumbnail(string $originalImagePath): string
{
$this->thumbnails[$originalImagePath] = self::FAKE_THUMBNAIL_PATH;
return self::FAKE_THUMBNAIL_PATH;
}
@@ -36,4 +38,4 @@ class InMemoryImageProcessor implements ImageProcessorInterface
{
return $this->thumbnails;
}
}
}

View File

@@ -48,4 +48,4 @@ class InMemoryMangaProvider implements MangaProviderInterface
{
return new MangaCollection([]);
}
}
}

View File

@@ -30,7 +30,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
$valueA = $this->getPropertyValue($a, $sortBy);
$valueB = $this->getPropertyValue($b, $sortBy);
return $sortOrder === 'asc'
return 'asc' === $sortOrder
? $valueA <=> $valueB
: $valueB <=> $valueA;
});
@@ -57,6 +57,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return $manga;
}
}
return null;
}
@@ -105,10 +106,10 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
private function getPropertyValue(Manga $manga, string $property): mixed
{
return match($property) {
return match ($property) {
'title' => $manga->getTitle()->getValue(),
'publicationYear' => $manga->getPublicationYear(),
default => throw new \InvalidArgumentException("Unknown sort property: $property")
default => throw new \InvalidArgumentException("Unknown sort property: $property"),
};
}
@@ -121,7 +122,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
$chapters = $this->chapters[$mangaId];
usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) {
return $sortOrder === 'desc'
return 'desc' === $sortOrder
? $b->getNumber() <=> $a->getNumber()
: $a->getNumber() <=> $b->getNumber();
});
@@ -138,12 +139,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
$chapters = $this->chapters[$mangaId];
usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) {
return $sortOrder === 'desc'
return 'desc' === $sortOrder
? $b->getNumber() <=> $a->getNumber()
: $a->getNumber() <=> $b->getNumber();
});
$offset = ($page - 1) * $limit;
return array_slice($chapters, $offset, $limit);
}
@@ -171,6 +173,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
if ($chapter && $chapter->isVisible()) {
return $chapter;
}
return null;
}
@@ -181,6 +184,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return $chapter;
}
}
return null;
}
@@ -196,10 +200,9 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
{
return array_values(array_filter(
$this->chaptersById,
fn (Chapter $chapter) =>
$chapter->getMangaId()->getValue() === $mangaId &&
$chapter->getVolume() === $volume &&
$chapter->isVisible()
fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId
&& $chapter->getVolume() === $volume
&& $chapter->isVisible()
));
}
@@ -207,11 +210,10 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
{
return array_values(array_filter(
$this->chaptersById,
fn (Chapter $chapter) =>
$chapter->getMangaId()->getValue() === $mangaId &&
$chapter->getVolume() === $volume &&
$chapter->isVisible() &&
$chapter->isAvailable()
fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId
&& $chapter->getVolume() === $volume
&& $chapter->isVisible()
&& $chapter->isAvailable()
));
}
@@ -228,13 +230,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
{
$this->chapters[$mangaId] = [];
for ($i = 1; $i <= $count; $i++) {
for ($i = 1; $i <= $count; ++$i) {
$chapter = new Chapter(
id: new ChapterId((string)$i),
id: new ChapterId((string) $i),
mangaId: new MangaId($mangaId),
number: (float)$i,
number: (float) $i,
title: "Chapter $i",
volume: (int)ceil($i / 10),
volume: (int) ceil($i / 10),
isVisible: true,
createdAt: new \DateTimeImmutable()
);
@@ -250,6 +252,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return $manga;
}
}
return null;
}
@@ -267,7 +270,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
$manga->getTitle()->getValue(),
$manga->getSlug()->getValue(),
$manga->getAuthor(),
$manga->getDescription()
$manga->getDescription(),
];
foreach ($manga->getAlternativeSlugs() as $altSlug) {
@@ -287,6 +290,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
usort($sortedMangas, fn (Manga $a, Manga $b) => $a->getTitle()->getValue() <=> $b->getTitle()->getValue());
$offset = ($page - 1) * $limit;
return array_slice($sortedMangas, $offset, $limit);
}
@@ -320,9 +324,9 @@ class InMemoryMangaRepository implements MangaRepositoryInterface
return false;
}
if ($criteria->lastCheckBefore !== null) {
if (null !== $criteria->lastCheckBefore) {
$lastCheck = $manga->getLastMonitoringCheck();
if ($lastCheck === null || $lastCheck >= $criteria->lastCheckBefore) {
if (null === $lastCheck || $lastCheck >= $criteria->lastCheckBefore) {
return false;
}
}

View File

@@ -13,7 +13,7 @@ class InMemoryMangadexClient implements MangadexClientInterface
public function __construct(
array $mangas = [],
array $feeds = [],
array $aggregates = []
array $aggregates = [],
) {
$this->mangas = $mangas;
$this->feeds = $feeds;
@@ -34,8 +34,8 @@ class InMemoryMangadexClient implements MangadexClientInterface
{
$results = [];
foreach ($this->mangas as $id => $manga) {
if (isset($manga['attributes']['title']['en']) &&
str_contains(
if (isset($manga['attributes']['title']['en'])
&& str_contains(
strtolower($manga['attributes']['title']['en']),
strtolower($title)
)
@@ -52,7 +52,7 @@ class InMemoryMangadexClient implements MangadexClientInterface
$statistics = [];
foreach ($mangaIds as $id) {
$statistics[$id] = [
'rating' => ['average' => 4.5] // Default rating for tests
'rating' => ['average' => 4.5], // Default rating for tests
];
}
@@ -64,18 +64,18 @@ class InMemoryMangadexClient implements MangadexClientInterface
if (!isset($this->feeds[$mangaId])) {
return [
'data' => [],
'total' => 0
'total' => 0,
];
}
$feed = $this->feeds[$mangaId];
if ($order === 'desc') {
if ('desc' === $order) {
$feed = array_reverse($feed);
}
return [
'data' => array_slice($feed, $offset, $limit),
'total' => count($feed)
'total' => count($feed),
];
}
@@ -84,13 +84,13 @@ class InMemoryMangadexClient implements MangadexClientInterface
if (!isset($this->aggregates[$mangaId])) {
return [
'result' => 'ok',
'volumes' => []
'volumes' => [],
];
}
return [
'result' => 'ok',
'volumes' => $this->aggregates[$mangaId]
'volumes' => $this->aggregates[$mangaId],
];
}
@@ -102,7 +102,7 @@ class InMemoryMangadexClient implements MangadexClientInterface
return [
'result' => 'ok',
'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId])
'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId]),
];
}
@@ -125,4 +125,4 @@ class InMemoryMangadexClient implements MangadexClientInterface
{
$this->aggregates[$mangaId] = $aggregate;
}
}
}

View File

@@ -11,28 +11,31 @@ class InMemoryPathManager implements MangaPathManagerInterface
public function getMangaDirectory(string $mangaTitle, string $publicationYear): string
{
$dir = '/tmp/manga/' . $this->slugify($mangaTitle) . '_' . $publicationYear;
$dir = '/tmp/manga/'.$this->slugify($mangaTitle).'_'.$publicationYear;
$this->ensureDirectory($dir);
return $dir;
}
public function getVolumeDirectory(string $mangaTitle, string $publicationYear, int $volumeNumber): string
{
$dir = $this->getMangaDirectory($mangaTitle, $publicationYear) . '/volume_' . $volumeNumber;
$dir = $this->getMangaDirectory($mangaTitle, $publicationYear).'/volume_'.$volumeNumber;
$this->ensureDirectory($dir);
return $dir;
}
public function buildChapterCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber, string $chapterNumber): string
{
$dir = $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber);
return $dir . '/' . $this->slugify($mangaTitle) . '_vol' . $volumeNumber . '_ch' . $chapterNumber . '.cbz';
return $dir.'/'.$this->slugify($mangaTitle).'_vol'.$volumeNumber.'_ch'.$chapterNumber.'.cbz';
}
public function buildVolumeCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber): string
{
return $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber)
. '/' . $this->slugify($mangaTitle) . '_vol' . $volumeNumber . '.cbz';
.'/'.$this->slugify($mangaTitle).'_vol'.$volumeNumber.'.cbz';
}
public function createCbzArchive(array $files, string $cbzPath): void
@@ -56,7 +59,7 @@ class InMemoryPathManager implements MangaPathManagerInterface
}
/**
* Get all stored files
* Get all stored files.
*/
public function getFiles(): array
{
@@ -64,7 +67,7 @@ class InMemoryPathManager implements MangaPathManagerInterface
}
/**
* Clear all stored files
* Clear all stored files.
*/
public function clear(): void
{
@@ -79,6 +82,7 @@ class InMemoryPathManager implements MangaPathManagerInterface
$text = trim($text, '-');
$text = preg_replace('~-+~', '-', $text);
$text = strtolower($text);
return $text ?: 'n-a';
}