Symfony-hexagonal-skill symfony-cqrs-handlers
Symfony CQRS command/query handlers — commands, queries, handlers, bus configuration, use cases. Triggers on: command, query, handler, CQRS, bus, use case, command handler, query handler, message bus
install
source · Clone the upstream repo
git clone https://github.com/aligundogdu/symfony-hexagonal-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aligundogdu/symfony-hexagonal-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/symfony-cqrs-handlers" ~/.claude/skills/aligundogdu-symfony-hexagonal-skill-symfony-cqrs-handlers && rm -rf "$T"
manifest:
skills/symfony-cqrs-handlers/SKILL.mdsource content
Symfony CQRS Handlers
You are an expert in CQRS (Command Query Responsibility Segregation) within Symfony hexagonal architecture.
When to Activate
- User wants to create a command or query
- User needs a handler for a use case
- User asks about CQRS patterns or message bus configuration
- User mentions "use case", "action", "operation" in application context
Command Pattern
Commands represent write operations (create, update, delete). They are DTOs dispatched to the command bus.
Rules
— immutable after constructionfinal readonly class- Named as imperative verb:
,RegisterUser
,PlaceOrderCancelSubscription - Contains only primitive types and value objects — no entities
- Handler returns
or a scalar identifier (string ID)void - One handler per command
Template
namespace App\Application\{Module}\Command; final readonly class {ActionVerb}{Entity} { public function __construct( public string $param1, public string $param2, // only primitives and simple types ) { } }
Handler Template
namespace App\Application\{Module}\Command; use App\Domain\{Module}\Port\{Repository}Interface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; #[AsMessageHandler(bus: 'command.bus')] final readonly class {ActionVerb}{Entity}Handler { public function __construct( private {Repository}Interface $repository, ) { } public function __invoke({ActionVerb}{Entity} $command): void { // 1. Reconstruct/create domain objects // 2. Execute business logic // 3. Persist via port // NO side-effects here — use domain events } }
Query Pattern
Queries represent read operations. They return DTOs, never domain entities.
Rules
— immutablefinal readonly class- Named descriptively:
,GetUserById
,ListActiveOrdersSearchProducts - Handler MUST return a DTO or array of DTOs
- Handler NEVER modifies state
- May use read-optimized ports (separate from write ports)
Template
namespace App\Application\{Module}\Query; final readonly class {GetDescription} { public function __construct( public string $identifier, // filter/pagination params ) { } }
Handler Template
namespace App\Application\{Module}\Query; use App\Application\{Module}\DTO\{Entity}DTO; use App\Domain\{Module}\Port\{Repository}Interface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; #[AsMessageHandler(bus: 'query.bus')] final readonly class {GetDescription}Handler { public function __construct( private {Repository}Interface $repository, ) { } public function __invoke({GetDescription} $query): ?{Entity}DTO { $entity = $this->repository->findById($query->identifier); if ($entity === null) { return null; } return {Entity}DTO::fromEntity($entity); } }
DTO Pattern
namespace App\Application\{Module}\DTO; final readonly class {Entity}DTO { public function __construct( public string $id, public string $field1, public string $field2, ) { } public static function fromEntity(/* entity */): self { return new self( id: (string) $entity->id(), field1: $entity->field1(), field2: $entity->field2(), ); } }
References
See
references/ for detailed guides:
— Full command examples with validationcommand-patterns.md
— Query patterns with pagination and filteringquery-patterns.md
— Messenger bus setup and middlewarebus-configuration.md