4.0 KiB
4.0 KiB
name, description, allowed-tools
| name | description | allowed-tools |
|---|---|---|
| ddd-core | 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/. | 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ésprivate. - 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.).
// ✅ 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).
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 :
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
MessageBusInterfacede Symfony Messenger (autorisé dans Application, pas dans Domain).
readonly class MangaCreated
{
public function __construct(
public string $mangaId,
public string $externalId,
) {}
}
Domain Exceptions
- Étendent
DomainExceptionou\RuntimeExceptionselon le cas. - Nommées avec le suffixe
ExceptionouNotFoundException. - Localisées dans
Domain/Exception/.
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\UuidetSymfony\Component\Messenger: autorisés uniquement en Application, pas en Domain.
Vérification : make phparkitect