feat: ajout de claude + correction des tests
All checks were successful
Build and Deploy / deploy (push) Successful in 9m36s

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-09 17:09:31 +01:00
parent b5a832fbbc
commit dae215dd3d
8 changed files with 1094 additions and 24 deletions

View File

@@ -0,0 +1,144 @@
---
name: ddd-core
description: Règles DDD du projet Mangarr — Aggregates, Value Objects immutables, Domain Events, invariants. Utiliser quand on crée ou modifie un Model, Value Object, Event ou Exception dans src/Domain/*/Domain/.
allowed-tools: Read, Grep, Glob
---
# Règles DDD — Couche Domain
## Emplacement
```
src/Domain/{DomainName}/Domain/
Model/
{AggregateName}.php
ValueObject/
{VoName}.php
Event/
{SomethingHappened}.php
Exception/
{Something}Exception.php
Contract/
Repository/
{Name}RepositoryInterface.php
Service/
{Name}Interface.php
Client/
{Name}ClientInterface.php
```
## Aggregates
- Classe normale (pas `readonly`), propriétés `private`.
- Le constructeur prend des **Value Objects**, jamais des scalaires bruts pour les identifiants et concepts métier.
- **Aucune annotation Doctrine** dans le Model — c'est la responsabilité du Repository (Infrastructure).
- Les méthodes métier protègent les invariants et lèvent des **Domain Exceptions** (jamais des exceptions génériques).
- Les setters publics sont interdits. Exposer des méthodes métier explicites (`updateImageUrls()`, `enableMonitoring()`, etc.).
```php
// ✅ Correct
class Manga
{
public function __construct(
private MangaId $id,
private MangaTitle $title,
private MangaSlug $slug,
// ...
) {}
public function updateImageUrls(ImageUrls $imageUrls): void
{
$this->imageUrls = $imageUrls;
}
}
// ❌ Interdit
class Manga
{
public string $title; // propriété publique
#[ORM\Column] // annotation Doctrine dans le Domain
private string $title;
public function setTitle(string $title): void {} // setter générique
}
```
## Value Objects
- Toujours `readonly class`.
- Valider dans le constructeur, lever une **Domain Exception** si invalide.
- Exposer `getValue()` pour récupérer la valeur primitive.
- Jamais de dépendance externe (pas de Symfony, pas de Doctrine).
```php
readonly class MangaTitle
{
public function __construct(public readonly string $value)
{
if (empty(trim($value))) {
throw new InvalidMangaTitleException('Title cannot be empty');
}
}
public function getValue(): string
{
return $this->value;
}
}
```
Valeurs composées (ex: chemins d'images) → Value Object avec plusieurs propriétés :
```php
readonly class ImageUrls
{
public function __construct(
private string $full,
private string $thumbnail,
) {}
public function getFull(): string { return $this->full; }
public function getThumbnail(): string { return $this->thumbnail; }
}
```
## Domain Events
- Nommés au **passé** : `MangaCreated`, `ChapterImported`, `MonitoringEnabled`.
- `readonly class`, transportent uniquement des scalaires (pas d'objets du Domain).
- Placés dans `Domain/Event/`.
- Dispatchés depuis le **CommandHandler** (Application), jamais depuis le Domain lui-même.
- Le bus utilisé est `MessageBusInterface` de Symfony Messenger (autorisé dans Application, pas dans Domain).
```php
readonly class MangaCreated
{
public function __construct(
public string $mangaId,
public string $externalId,
) {}
}
```
## Domain Exceptions
- Étendent `DomainException` ou `\RuntimeException` selon le cas.
- Nommées avec le suffixe `Exception` ou `NotFoundException`.
- Localisées dans `Domain/Exception/`.
```php
class MangaNotFoundException extends \DomainException
{
public function __construct()
{
parent::__construct('Manga not found');
}
}
```
## Règles d'invariants PHPArkitect (enforced automatiquement)
- `App\Domain\{X}\Domain`**aucune dépendance** en dehors de son propre namespace.
- Exceptions autorisées : `DateTimeImmutable`, `RuntimeException`, `Exception`, `DomainException`, `InvalidArgumentException`, `Throwable`, `Symfony\Component\HttpKernel\Exception`.
- `Ramsey\Uuid` et `Symfony\Component\Messenger` : autorisés uniquement en **Application**, pas en Domain.
Vérification : `make phparkitect`