--- description: globs: *.php,src/* alwaysApply: false --- # Architecture Hexagonale de Mangarr ## Structure Générale L'application suit une architecture hexagonale (ports & adapters) avec une séparation claire des responsabilités. Le code métier est organisé en domaines distincts dans le dossier `src/Domain/`. ``` src/ └── Domain/ ├── Shared/ # Code partagé entre les domaines ├── Manga/ # Domaine de gestion des mangas ├── Reader/ # Domaine de lecture └── Scraping/ # Domaine de scraping ``` ## Organisation des Domaines Chaque domaine suit la même structure hexagonale : ``` Domain/Manga/ ├── Domain/ # Cœur métier │ ├── Entity/ # Entités du domaine │ ├── ValueObject/ # Objets de valeur │ ├── Event/ # Événements du domaine │ └── Exception/ # Exceptions métier ├── Application/ # Cas d'utilisation │ ├── Command/ # Commandes (DTO) │ ├── CommandHandler/# Gestionnaires de commandes │ ├── Query/ # Requêtes (DTO) │ ├── QueryHandler/ # Gestionnaires de requêtes │ └── Response/ # Objets de réponse (DTO) └── Infrastructure/ # Adaptateurs ├── Repository/ # Implémentation des repositories ├── Service/ # Services techniques └── Persistence/ # Persistence des données ``` ## Règles d'Architecture ### 1. Règles Générales - Tout le code métier doit résider dans le namespace `App\Domain` - Les dépendances externes doivent être limitées et explicitement autorisées - Les exceptions standards et utilitaires autorisés : - `DateTimeImmutable` - `RuntimeException` - `Exception` - `DomainException` - `Symfony\Component\HttpKernel\Exception` - `InvalidArgumentException` ### 2. Domaine Shared - Le domaine `Shared` ne doit dépendre d'aucun autre domaine - Il contient les contrats et les types partagés entre les domaines - Exemple : `App\Domain\Shared\Contract\UuidInterface` ### 3. Couche Domain - Ne doit dépendre que d'elle-même et du domaine Shared - Contient la logique métier pure - Ne doit pas avoir de dépendances externes - Structure des composants : - Les `Entity` sont les objets métier principaux - Les `ValueObject` sont immuables et s'auto-valident - Les `Event` représentent les changements d'état du domaine - Les `Exception` définissent les erreurs métier spécifiques ### 4. Couche Application - Peut dépendre de son propre domaine et du domaine Shared - Peut utiliser les dépendances externes autorisées : - `Symfony\Component\Messenger` - `Ramsey\Uuid` - Ne doit JAMAIS dépendre de la couche Infrastructure - Structure des composants : - Les `Query` sont des DTO (Data Transfer Objects) en lecture seule - Les `Command` sont des DTO pour les modifications - Les `QueryHandler` doivent : - Implémenter `QueryHandlerInterface` - Prendre une seule `Query` en paramètre - Retourner une `Response` - Les `CommandHandler` doivent : - Implémenter `CommandHandlerInterface` - Prendre une seule `Command` en paramètre - Ne pas retourner de valeur (void) - Les `Response` sont des DTO immuables pour les résultats de requêtes ### 5. Couche Infrastructure - Implémente les interfaces définies dans le domaine - Peut dépendre de toutes les couches de son domaine - Contient les adaptateurs pour les services externes - Structure des composants : - Les `Repository` implémentent les interfaces du domaine - Les `Service` fournissent des fonctionnalités techniques - La `Persistence` gère le stockage des données ## Flux de Dépendances ``` Infrastructure → Application → Domain ↓ ↓ ↓ External Shared Shared ``` ## Validation Les règles d'architecture sont validées par phparkitect. Les violations de ces règles entraîneront une erreur lors de la validation. ## Exemples de Code ### Domain Layer ```php namespace App\Domain\Manga\Domain\Entity; class Manga { private MangaId $id; private Title $title; private Description $description; public function __construct(MangaId $id, Title $title) { $this->id = $id; $this->title = $title; } } ``` ### Application Layer ```php namespace App\Domain\Manga\Application\Query; readonly class GetMangaByIdQuery { public function __construct( public string $id ) {} } namespace App\Domain\Manga\Application\QueryHandler; class GetMangaByIdQueryHandler implements QueryHandlerInterface { public function __construct( private MangaRepositoryInterface $mangaRepository ) {} public function __invoke(GetMangaByIdQuery $query): MangaResponse { $manga = $this->mangaRepository->get($query->id); return new MangaResponse($manga); } } namespace App\Domain\Manga\Application\Command; readonly class CreateMangaCommand { public function __construct( public string $title, public ?string $description = null, ) {} } namespace App\Domain\Manga\Application\CommandHandler; class CreateMangaCommandHandler implements CommandHandlerInterface { public function __construct( private MangaRepositoryInterface $mangaRepository ) {} public function __invoke(CreateMangaCommand $command): void { $manga = Manga::create($command->title, $command->description); $this->mangaRepository->save($manga); } } namespace App\Domain\Manga\Application\Response; readonly class MangaResponse { public function __construct( public string $id, public string $title, public ?string $description ) {} public static function fromEntity(Manga $manga): self { return new self( $manga->getId()->toString(), $manga->getTitle()->value(), $manga->getDescription()?->value() ); } } ``` ### Infrastructure Layer ```php namespace App\Domain\Manga\Infrastructure\Repository; use App\Domain\Manga\Domain\Repository\MangaRepositoryInterface; class DoctrineMangaRepository implements MangaRepositoryInterface { // Implementation } ```