Skip to content

/symfony-domain-modeling

Symfony 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

shell
$ npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-domain-modeling --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/symfony-domain-modeling
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Symfony 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

SKILL.md

symfony-domain-modeling.SKILL.md
description: "Symfony 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"

Symfony Domain Modeling

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.

When to Activate

  • User wants to create an entity or aggregate root
  • User asks about value objects
  • User needs domain events
  • User mentions domain exceptions or business rules
  • User discusses aggregates or invariants

Entity Patterns

Rules

  • Private constructor + static factory method(s)
  • No Doctrine annotations/attributes — mapping is in Infrastructure
  • Record domain events on state changes
  • Protect invariants in methods, not in external validators
  • Use value objects for typed properties (no primitive obsession)

Template

namespace App\Domain\{Module}\Entity;

final class {Entity}
{
    private array $domainEvents = [];

    private function __construct(
        private {EntityId} $id,
        // typed properties using value objects
    ) {
    }

    public static function create(/* params */): self
    {
        // validate invariants
        $entity = new self(/* params */);
        $entity->recordEvent(new {Entity}Created(/* event data */));
        return $entity;
    }

    // Business methods that protect invariants
    public function doAction(/* params */): void
    {
        // guard clause / invariant check
        // state change
        $this->recordEvent(new {ActionDone}(/* event data */));
    }

    public function pullDomainEvents(): array
    {
        $events = $this->domainEvents;
        $this->domainEvents = [];
        return $events;
    }

    private function recordEvent(object $event): void
    {
        $this->domainEvents[] = $event;
    }
}

Value Object Patterns

Rules

  • Always `final readonly class`
  • Self-validating in constructor (throw domain exception if invalid)
  • Immutable — no setters
  • Implement `equals()` for comparison
  • Use for: IDs, email, money, dates, status enums

Template

namespace App\Domain\{Module}\ValueObject;

use App\Domain\{Module}\Exception\Invalid{ValueObject}Exception;

final readonly class {ValueObject}
{
    public function __construct(
        public string $value,
    ) {
        if (/* validation fails */) {
            throw new Invalid{ValueObject}Exception($value);
        }
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return $this->value;
    }
}

Domain Event Patterns

Rules

  • Past-tense naming: `UserRegistered`, `OrderPlaced`, `PaymentFailed`
  • Immutable (`final readonly class`)
  • Carry only the data needed by handlers (IDs, not full entities)
  • Created inside entities on state changes

Template

namespace App\Domain\{Module}\Event;

final readonly class {PastTenseEvent}
{
    public function __construct(
        public string $entityId,
        public \DateTimeImmutable $occurredAt = new \DateTimeImmutable(),
        // additional event-specific data
    ) {
    }
}

Domain Exception Patterns

namespace App\Domain\{Module}\Exception;

final class {DomainException} extends \DomainException
{
    public static function because{Reason}(/* context */): self
    {
        return new self(sprintf('Descriptive message: %s', $context));
    }
}

References

See `references/` for detailed patterns:

  • `entity-patterns.md` — Full entity examples with aggregates
  • `value-object-patterns.md` — Common value object implementations
  • `domain-event-patterns.md` — Event design and dispatch patterns
Read more
Read it on GitHub ↗
Ships withsymfony-hexagonal-skill

A Claude Code plugin that enforces hexagonal architecture (ports & adapters) in Symfony projects. Works with both new projects (full scaffolding) and existing projects (progressive, module-by-module refactoring).

Get the whole plugin, auto-invoked

Other skills on symfony-hexagonal-skill.