$npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-domain-modelingSymfony domain modeling — entities, value objects, domain events, aggregates, domain exceptions. Triggers on: entity, value object, domain event, aggregate, domain exception, domain model, domain logic, business rule, invariant
| 1 | # Symfony Domain Modeling |
| 2 | |
| 3 | You are an expert in Domain-Driven Design within Symfony hexagonal architecture. Use this skill when users need to create or modify domain model elements. |
| 4 | |
| 5 | ## When to Activate |
| 6 | |
| 7 | - User wants to create an entity or aggregate root |
| 8 | - User asks about value objects |
| 9 | - User needs domain events |
| 10 | - User mentions domain exceptions or business rules |
| 11 | - User discusses aggregates or invariants |
| 12 | |
| 13 | ## Entity Patterns |
| 14 | |
| 15 | ### Rules |
| 16 | - Private constructor + static factory method(s) |
| 17 | - No Doctrine annotations/attributes — mapping is in Infrastructure |
| 18 | - Record domain events on state changes |
| 19 | - Protect invariants in methods, not in external validators |
| 20 | - Use value objects for typed properties (no primitive obsession) |
| 21 | |
| 22 | ### Template |
| 23 | ```php |
| 24 | namespace App\Domain\{Module}\Entity; |
| 25 | |
| 26 | final class {Entity} |
| 27 | { |
| 28 | private array $domainEvents = []; |
| 29 | |
| 30 | private function __construct( |
| 31 | private {EntityId} $id, |
| 32 | // typed properties using value objects |
| 33 | ) { |
| 34 | } |
| 35 | |
| 36 | public static function create(/* params */): self |
| 37 | { |
| 38 | // validate invariants |
| 39 | $entity = new self(/* params */); |
| 40 | $entity->recordEvent(new {Entity}Created(/* event data */)); |
| 41 | return $entity; |
| 42 | } |
| 43 | |
| 44 | // Business methods that protect invariants |
| 45 | public function doAction(/* params */): void |
| 46 | { |
| 47 | // guard clause / invariant check |
| 48 | // state change |
| 49 | $this->recordEvent(new {ActionDone}(/* event data */)); |
| 50 | } |
| 51 | |
| 52 | public function pullDomainEvents(): array |
| 53 | { |
| 54 | $events = $this->domainEvents; |
| 55 | $this->domainEvents = []; |
| 56 | return $events; |
| 57 | } |
| 58 | |
| 59 | private function recordEvent(object $event): void |
| 60 | { |
| 61 | $this->domainEvents[] = $event; |
| 62 | } |
| 63 | } |
| 64 | ``` |
| 65 | |
| 66 | ## Value Object Patterns |
| 67 | |
| 68 | ### Rules |
| 69 | - Always `final readonly class` |
| 70 | - Self-validating in constructor (throw domain exception if invalid) |
| 71 | - Immutable — no setters |
| 72 | - Implement `equals()` for comparison |
| 73 | - Use for: IDs, email, money, dates, status enums |
| 74 | |
| 75 | ### Template |
| 76 | ```php |
| 77 | namespace App\Domain\{Module}\ValueObject; |
| 78 | |
| 79 | use App\Domain\{Module}\Exception\Invalid{ValueObject}Exception; |
| 80 | |
| 81 | final readonly class {ValueObject} |
| 82 | { |
| 83 | public function __construct( |
| 84 | public string $value, |
| 85 | ) { |
| 86 | if (/* validation fails */) { |
| 87 | throw new Invalid{ValueObject}Exception($value); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | public function equals(self $other): bool |
| 92 | { |
| 93 | return $this->value === $other->value; |
| 94 | } |
| 95 | |
| 96 | public function __toString(): string |
| 97 | { |
| 98 | return $this->value; |
| 99 | } |
| 100 | } |
| 101 | ``` |
| 102 | |
| 103 | ## Domain Event Patterns |
| 104 | |
| 105 | ### Rules |
| 106 | - Past-tense naming: `UserRegistered`, `OrderPlaced`, `PaymentFailed` |
| 107 | - Immutable (`final readonly class`) |
| 108 | - Carry only the data needed by handlers (IDs, not full entities) |
| 109 | - Created inside entities on state changes |
| 110 | |
| 111 | ### Template |
| 112 | ```php |
| 113 | namespace App\Domain\{Module}\Event; |
| 114 | |
| 115 | final readonly class {PastTenseEvent} |
| 116 | { |
| 117 | public function __construct( |
| 118 | public string $entityId, |
| 119 | public \DateTimeImmutable $occurredAt = new \DateTimeImmutable(), |
| 120 | // additional event-specific data |
| 121 | ) { |
| 122 | } |
| 123 | } |
| 124 | ``` |
| 125 | |
| 126 | ## Domain Exception Patterns |
| 127 | |
| 128 | ```php |
| 129 | namespace App\Domain\{Module}\Exception; |
| 130 | |
| 131 | final class {DomainException} extends \DomainException |
| 132 | { |
| 133 | public static function because{Reason}(/* context */): self |
| 134 | { |
| 135 | return new self(sprintf('Descriptive message: %s', $context)); |
| 136 | } |
| 137 | } |
| 138 | ``` |
| 139 | |
| 140 | ## References |
| 141 | |
| 142 | See `references/` for detailed patterns: |
| 143 | - `entity-patterns.md` — Full entity examples with aggregates |
| 144 | - `value-object-patterns.md` — Common value object implementations |
| 145 | - `domain-event-patterns.md` — Event design and dispatch patterns |