symfony-api-response
Symfony REST API response standard {result, error, extra, status}: thin controllers, #[MapRequestPayload] / #[MapQueryString]…
Symfony CQRS with Messenger: commands, queries, handlers, command.bus / query.bus / event.bus configuration, typed bus wrappers, HandlerFailedException unwrapping, read ports and pagination, use cases. Works on Symfony 6.4, 7.x, 8.x. Use when: command, query, handler, CQRS, bus,
Claude Code
$ npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers --agent claude-code
Codex
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers -a codex -y
Cursor
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers -a cursor -y
Gemini CLI
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers -a gemini-cli -y
GitHub Copilot
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers -a github-copilot -y
OpenCode
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers -a opencode -y
Windsurf
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-cqrs-handlers -a windsurf -y
name: symfony-cqrs-handlers description: "Symfony CQRS with Messenger: commands, queries, handlers, command.bus / query.bus / event.bus configuration, typed bus wrappers, HandlerFailedException unwrapping, read ports and pagination, use cases. Works on Symfony 6.4, 7.x, 8.x. Use when: command, query, handler, CQRS, bus, use case, command handler, query handler, message bus, HandleTrait, HandledStamp"
You are an expert in CQRS (Command Query Responsibility Segregation) within Symfony hexagonal architecture.
Symfony's docs recommend a single bus unless you need different middleware. This architecture has that need: `command.bus` wraps handlers in `doctrine_transaction`, `query.bus` must never open write transactions, and `event.bus` must allow zero handlers. Mention this when users question the extra buses.
| Feature | Requires | Fallback | |---|---|---| | `#[AsMessageHandler(bus: '...')]` | Symfony 6.0 | - | | `#[AsMessage('async')]` on a command | Symfony 7.2 | `framework.messenger.routing` in YAML | | `DeduplicateStamp` | Symfony 7.3 + symfony/lock | Idempotency key check in the handler | | `#[AsMessage(serializedTypeName: ...)]` | Symfony 8.1 | default FQCN type name |
Commands represent write operations (create, update, delete). They are messages dispatched to the command bus.
namespace App\Application\{Module}\Command;
use Symfony\Component\Validator\Constraints as Assert;
final readonly class {ActionVerb}{Entity}
{
public function __construct(
#[Assert\NotBlank]
public string $param1,
public string $param2,
) {
}
}namespace App\Application\{Module}\Command;
use App\Domain\{Module}\Port\{Entity}RepositoryInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler(bus: 'command.bus')]
final readonly class {ActionVerb}{Entity}Handler
{
public function __construct(
private {Entity}RepositoryInterface $repository,
) {
}
public function __invoke({ActionVerb}{Entity} $command): void
{
// 1. Load/create domain objects
// 2. Execute business logic (entity methods)
// 3. Persist via port (repository dispatches domain events)
// NO side-effects here, use domain events
}
}Queries represent read operations. They return DTOs, never domain entities.
namespace App\Application\{Module}\Query;
final readonly class Get{Entity}ById
{
public function __construct(
public string $id,
) {
}
}namespace App\Application\{Module}\Query;
use App\Application\{Module}\DTO\{Entity}DTO;
use App\Domain\{Module}\Port\{Entity}RepositoryInterface;
use App\Domain\{Module}\ValueObject\{Entity}Id;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler(bus: 'query.bus')]
final readonly class Get{Entity}ByIdHandler
{
public function __construct(
private {Entity}RepositoryInterface $repository,
) {
}
public function __invoke(Get{Entity}ById $query): ?{Entity}DTO
{
$entity = $this->repository->findById(new {Entity}Id($query->id));
return $entity === null ? null : {Entity}DTO::fromEntity($entity);
}
}namespace App\Application\{Module}\DTO;
use App\Domain\{Module}\Entity\{Entity};
final readonly class {Entity}DTO
{
public function __construct(
public string $id,
public string $name,
) {
}
public static function fromEntity({Entity} $entity): self
{
return new self(
id: $entity->id()->value,
name: $entity->name(),
);
}
}Symfony 7.3+ ObjectMapper (`#[Map]`) can replace hand-written `fromX()` factories for DTO-to-DTO mapping. Do not map directly into Domain entities with it: it bypasses constructors and therefore invariants.
Use the `CommandBus` / `QueryBus` wrappers from `references/bus-configuration.md`. They return the handler result and rethrow the original domain exception instead of Messenger's `HandlerFailedException`, so the API exception subscriber can map it to the right HTTP status.
See `references/` for detailed guides:
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).
Symfony REST API response standard {result, error, extra, status}: thin controllers, #[MapRequestPayload] / #[MapQueryString]…
Doctrine persistence for Symfony hexagonal architecture (ORM 2.x/3.x, DBAL 3/4, DoctrineBundle 2/3): repository adapters, XML…
Symfony DDD domain modeling in pure PHP: entities, aggregates, value objects, domain events, domain exceptions, ID generation…
Symfony hexagonal architecture (ports & adapters) setup for Symfony 6.4 LTS, 7.4 LTS and 8.x: project structure, module…
Symfony Messenger async processing and Symfony Scheduler instead of cron: transports, routing (#[AsMessage] 7.2+), retry with…
Symfony ports and adapters: port interfaces in Domain, adapter implementations in Infrastructure, dependency injection,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.