symfony-api-response
Symfony REST API response standard {result, error, extra, status}: thin controllers, #[MapRequestPayload] / #[MapQueryString]…
Symfony Messenger async processing and Symfony Scheduler instead of cron: transports, routing (#[AsMessage] 7.2+), retry with backoff and jitter, failure transport, idempotency keys, DeduplicateStamp (7.3+), workers, supervisor, scheduled commands with stateful/locked schedules.
Claude Code
$ npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async --agent claude-code
Codex
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async -a codex -y
Cursor
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async -a cursor -y
Gemini CLI
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async -a gemini-cli -y
GitHub Copilot
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async -a github-copilot -y
OpenCode
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async -a opencode -y
Windsurf
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-messenger-async -a windsurf -y
name: symfony-messenger-async description: "Symfony Messenger async processing and Symfony Scheduler instead of cron: transports, routing (#[AsMessage] 7.2+), retry with backoff and jitter, failure transport, idempotency keys, DeduplicateStamp (7.3+), workers, supervisor, scheduled commands with stateful/locked schedules. Use when: messenger, async, queue, retry, scheduler, background job, worker, transport, message queue, cron, scheduled task, idempotency, deduplication"
You are an expert in async message processing with Symfony Messenger within hexagonal architecture.
**Never use raw cron.** Always use:
| Feature | Requires | Fallback | |---|---|---| | Scheduler component, `RecurringMessage` | Symfony 6.3 | - (no cron) | | `#[AsCronTask]`, `#[AsPeriodicTask]` | Symfony 6.4 | `ScheduleProviderInterface` | | `retry_strategy.jitter` | Symfony 7.1 | omit | | `#[AsMessage('async')]` routing attribute | Symfony 7.2 | YAML `routing:` | | `DeduplicateStamp` (+ symfony/lock) | Symfony 7.3 | idempotency key + unique constraint | | `messenger:consume --keepalive` | Symfony 7.2/7.3 (transport dependent) | shorter `--time-limit` | | `#[AsMessageHandler(sign: true)]` message signing | Symfony 7.4 | trusted transport | | `messenger:consume --all --exclude-receivers=failed` | Symfony 7.4 | list receivers explicitly | | `#[AsMessage(serializedTypeName: ...)]`, `PriorityStamp`, regex receiver names | Symfony 8.1 | FQCN type names, separate priority transports |
// The command is the same: async is a routing concern, not a code concern
namespace App\Application\Report\Command;
final readonly class GenerateReport
{
public function __construct(
public string $reportId,
public string $userId,
public string $type,
) {
}
}
// Handler is identical to sync: the bus handles async dispatch
#[AsMessageHandler(bus: 'command.bus')]
final readonly class GenerateReportHandler
{
public function __invoke(GenerateReport $command): void
{
// Long-running operation
}
}# Route to async transport (all versions)
framework:
messenger:
routing:
'App\Application\Report\Command\GenerateReport': asyncSymfony 7.2+: `#[AsMessage('async')]` on the command class works too. Never put it on **domain events**: Domain must stay free of Symfony attributes, so events are always routed in YAML.
Every async handler MUST be idempotent: messages can be delivered more than once (retries, worker crashes, redeliveries).
#[AsMessageHandler(bus: 'command.bus')]
final readonly class ProcessPaymentHandler
{
public function __construct(
private PaymentRepositoryInterface $payments,
private PaymentGatewayInterface $gateway,
) {
}
public function __invoke(ProcessPayment $command): void
{
if ($this->payments->existsByIdempotencyKey($command->idempotencyKey)) {
return; // Already processed, skip
}
$result = $this->gateway->charge(
$command->customerId,
new Money($command->amount, $command->currency),
sprintf('Order %s', $command->orderId),
$command->idempotencyKey, // the provider deduplicates too
);
$this->payments->record($command->orderId, $result, $command->idempotencyKey);
}
}Symfony 7.3+ adds `DeduplicateStamp`, which prevents the **same message from being queued twice** while one is pending. It complements, but does not replace, handler idempotency (see `references/idempotency-patterns.md`).
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]…
Symfony CQRS with Messenger: commands, queries, handlers, command.bus / query.bus / event.bus configuration, typed bus wrappers,…
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 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.