feat: ajout de claude + correction des tests
All checks were successful
Build and Deploy / deploy (push) Successful in 9m36s
All checks were successful
Build and Deploy / deploy (push) Successful in 9m36s
This commit is contained in:
parent
b5a832fbbc
commit
dae215dd3d
144
.claude/skills/ddd-core/SKILL.md
Normal file
144
.claude/skills/ddd-core/SKILL.md
Normal 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`
|
||||
Reference in New Issue
Block a user