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

@@ -7,9 +7,10 @@ namespace App\Domain\Reader\Application\Query;
final class GetChapterContext
{
public function __construct(
private readonly string $chapterId
private readonly string $chapterId,
) {
}
public function getChapterId(): string
{
return $this->chapterId;

View File

@@ -8,8 +8,8 @@ final readonly class GetChapterPages
{
public function __construct(
private string $chapterId,
private int $page = 1,
private int $itemsPerPage = 20
private int $page = 1,
private int $itemsPerPage = 20,
) {
}

View File

@@ -12,7 +12,7 @@ use App\Domain\Reader\Domain\ValueObject\ChapterId;
final readonly class GetChapterContextHandler
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository
private ChapterRepositoryInterface $chapterRepository,
) {
}

View File

@@ -13,7 +13,7 @@ use App\Domain\Reader\Domain\ValueObject\ChapterId;
final readonly class GetChapterPagesHandler
{
public function __construct(
private ChapterRepositoryInterface $chapterRepository
private ChapterRepositoryInterface $chapterRepository,
) {
}
@@ -23,7 +23,7 @@ final readonly class GetChapterPagesHandler
$totalItems = $this->chapterRepository->getTotalPagesForChapter($chapterId);
if ($totalItems === 0) {
if (0 === $totalItems) {
throw ChapterNotFoundException::forChapter($chapterId);
}

View File

@@ -7,13 +7,13 @@ namespace App\Domain\Reader\Application\Response;
final readonly class ChapterContextResponse
{
public function __construct(
private string $id,
private string $mangaId,
private string $title,
private float $number,
private int $totalPages,
private string $id,
private string $mangaId,
private string $title,
private float $number,
private int $totalPages,
private ?string $previousChapterId,
private ?string $nextChapterId
private ?string $nextChapterId,
) {
}

View File

@@ -13,7 +13,7 @@ class Page
private readonly PageNumber $pageNumber,
private readonly string $url,
private readonly int $width,
private readonly int $height
private readonly int $height,
) {
}

View File

@@ -7,7 +7,7 @@ namespace App\Domain\Reader\Domain\ValueObject;
readonly class ChapterId
{
public function __construct(
private string $value
private string $value,
) {
}

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace App\Domain\Reader\Domain\ValueObject;
use InvalidArgumentException;
final class PageNumber
{
private int $value;
@@ -13,7 +11,7 @@ final class PageNumber
public function __construct(int $value)
{
if ($value < 1) {
throw new InvalidArgumentException('Le numéro de page doit être supérieur à 0');
throw new \InvalidArgumentException('Le numéro de page doit être supérieur à 0');
}
$this->value = $value;

View File

@@ -6,26 +6,27 @@ namespace App\Domain\Reader\Infrastructure\ApiPlatform\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\OpenApi\Model\Parameter;
use App\Domain\Reader\Infrastructure\ApiPlatform\State\Provider\ChapterContextProvider;
use Symfony\Component\Serializer\Annotation\Groups;
#[ApiResource(
shortName: 'Reader',
operations: [
new Get(
uriTemplate: '/reader/chapter/{chapterId}',
openapiContext: [
'summary' => 'Récupère le contexte d\'un chapitre',
'description' => 'Retourne les métadonnées du chapitre et sa navigation',
'parameters' => [
[
'name' => 'chapterId',
'in' => 'path',
'required' => true,
'schema' => ['type' => 'string'],
],
openapi: new Operation(
summary: 'Récupère le contexte d\'un chapitre',
description: 'Retourne les métadonnées du chapitre et sa navigation',
parameters: [
new Parameter(
name: 'chapterId',
in: 'path',
required: true,
schema: ['type' => 'string']
),
],
'responses' => [
responses: [
'200' => [
'description' => 'Contexte du chapitre',
'content' => [
@@ -40,8 +41,8 @@ use Symfony\Component\Serializer\Annotation\Groups;
'type' => 'object',
'properties' => [
'id' => ['type' => 'string'],
'title' => ['type' => 'string']
]
'title' => ['type' => 'string'],
],
],
'navigation' => [
'type' => 'object',
@@ -51,29 +52,29 @@ use Symfony\Component\Serializer\Annotation\Groups;
'nullable' => true,
'properties' => [
'id' => ['type' => 'string'],
'number' => ['type' => 'string']
]
'number' => ['type' => 'string'],
],
],
'next' => [
'type' => 'object',
'nullable' => true,
'properties' => [
'id' => ['type' => 'string'],
'number' => ['type' => 'string']
]
]
]
]
]
]
]
]
'number' => ['type' => 'string'],
],
],
],
],
],
],
],
],
],
'404' => [
'description' => 'Chapitre non trouvé'
]
'description' => 'Chapitre non trouvé',
],
]
],
),
provider: ChapterContextProvider::class
),
],

View File

@@ -6,38 +6,39 @@ namespace App\Domain\Reader\Infrastructure\ApiPlatform\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\OpenApi\Model\Parameter;
use App\Domain\Reader\Infrastructure\ApiPlatform\State\Provider\ChapterPagesProvider;
use Symfony\Component\Serializer\Annotation\Groups;
#[ApiResource(
shortName: 'Reader',
operations: [
new Get(
uriTemplate: '/reader/chapter/{chapterId}/pages',
openapiContext: [
'summary' => 'Récupère les pages d\'un chapitre',
'description' => 'Retourne une collection paginée des pages du chapitre',
'parameters' => [
[
'name' => 'chapterId',
'in' => 'path',
'required' => true,
'schema' => ['type' => 'string'],
],
[
'name' => 'page',
'in' => 'query',
'required' => false,
'schema' => ['type' => 'integer', 'default' => 1],
],
[
'name' => 'itemsPerPage',
'in' => 'query',
'required' => false,
'schema' => ['type' => 'integer', 'default' => 20],
],
openapi: new Operation(
summary: 'Récupère les pages d\'un chapitre',
description: 'Retourne une collection paginée des pages du chapitre',
parameters: [
new Parameter(
name: 'chapterId',
in: 'path',
required: true,
schema: ['type' => 'string']
),
new Parameter(
name: 'page',
in: 'query',
required: false,
schema: ['type' => 'integer', 'default' => 1]
),
new Parameter(
name: 'itemsPerPage',
in: 'query',
required: false,
schema: ['type' => 'integer', 'default' => 20]
),
],
'responses' => [
responses: [
'200' => [
'description' => 'Collection paginée des pages du chapitre',
'content' => [
@@ -55,26 +56,26 @@ use Symfony\Component\Serializer\Annotation\Groups;
'type' => 'object',
'properties' => [
'width' => ['type' => 'integer'],
'height' => ['type' => 'integer']
]
]
]
]
'height' => ['type' => 'integer'],
],
],
],
],
],
'totalItems' => ['type' => 'integer'],
'currentPage' => ['type' => 'integer'],
'itemsPerPage' => ['type' => 'integer'],
'totalPages' => ['type' => 'integer']
]
]
]
]
'totalPages' => ['type' => 'integer'],
],
],
],
],
],
'404' => [
'description' => 'Chapitre non trouvé'
]
'description' => 'Chapitre non trouvé',
],
]
],
),
provider: ChapterPagesProvider::class
),
],

View File

@@ -13,7 +13,7 @@ use App\Domain\Reader\Application\Response\ChapterContextResponse;
final readonly class ChapterContextProvider implements ProviderInterface
{
public function __construct(
private GetChapterContextHandler $handler
private GetChapterContextHandler $handler,
) {
}

View File

@@ -13,7 +13,7 @@ use App\Domain\Reader\Application\Response\ChapterPagesResponse;
final readonly class ChapterPagesProvider implements ProviderInterface
{
public function __construct(
private GetChapterPagesHandler $handler
private GetChapterPagesHandler $handler,
) {
}

View File

@@ -16,14 +16,14 @@ use Doctrine\ORM\EntityManagerInterface;
readonly class LegacyChapterRepository implements ChapterRepositoryInterface
{
public function __construct(
private EntityManagerInterface $entityManager
private EntityManagerInterface $entityManager,
) {
}
public function getPagesForChapter(ChapterId $chapterId, int $page = 1, int $itemsPerPage = 20): array
{
$chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([
'id' => $chapterId->getValue()
'id' => $chapterId->getValue(),
]);
$pagesDirectory = $chapter->getPagesDirectory();
@@ -38,7 +38,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
{
/** @var ChapterEntity $chapter */
$chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([
'id' => $chapterId->getValue()
'id' => $chapterId->getValue(),
]);
if (!$chapter) {
@@ -65,7 +65,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
public function getTotalPagesForChapter(ChapterId $chapterId): int
{
$chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([
'id' => $chapterId->getValue()
'id' => $chapterId->getValue(),
]);
if (!$chapter) {
@@ -83,7 +83,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
public function getPreviousChapterId(ChapterId $chapterId): ?ChapterId
{
$currentChapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([
'id' => $chapterId->getValue()
'id' => $chapterId->getValue(),
]);
$qb = $this->entityManager->createQueryBuilder();
@@ -95,10 +95,8 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL')
->orderBy('c.number', 'DESC')
->setMaxResults(1)
->setParameters([
'manga' => $currentChapter->getManga(),
'number' => $currentChapter->getNumber()
]);
->setParameter('manga', $currentChapter->getManga())
->setParameter('number', $currentChapter->getNumber());
$previousChapter = $qb->getQuery()->getOneOrNullResult();
@@ -108,7 +106,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
public function getNextChapterId(ChapterId $chapterId): ?ChapterId
{
$currentChapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([
'id' => $chapterId->getValue()
'id' => $chapterId->getValue(),
]);
$qb = $this->entityManager->createQueryBuilder();
@@ -120,10 +118,8 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL')
->orderBy('c.number', 'ASC')
->setMaxResults(1)
->setParameters([
'manga' => $currentChapter->getManga(),
'number' => $currentChapter->getNumber()
]);
->setParameter('manga', $currentChapter->getManga())
->setParameter('number', $currentChapter->getNumber());
$nextChapter = $qb->getQuery()->getOneOrNullResult();
@@ -132,7 +128,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
private function getImageFiles(string $pagesDirectory): array
{
$files = glob($pagesDirectory . '/*.{jpg,jpeg,png,webp,gif}', GLOB_BRACE) ?: [];
$files = glob($pagesDirectory.'/*.{jpg,jpeg,png,webp,gif}', GLOB_BRACE) ?: [];
sort($files);
return $files;
@@ -145,9 +141,9 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface
$end = min($start + $itemsPerPage, count($files));
$pages = [];
for ($i = $start; $i < $end; $i++) {
for ($i = $start; $i < $end; ++$i) {
$imageSize = @getimagesize($files[$i]);
if ($imageSize === false) {
if (false === $imageSize) {
continue;
}