Troubleshooting
This guide covers common issues when integrating the CQRS bundle and how to resolve them.
1. Handler not found
Symptom
One of the following errors at runtime or compile time:
Runtime (dispatch):
NoHandlerException: No handler found for "App\Application\Command\CreateTask" dispatched on the messenger.bus.commands bus.
Compile time (container build):
LogicException: CQRS handler validation failed:
Command App\Application\Command\CreateTask has no handler registered.
Cause
The handler is not registered for the message. Common reasons:
- Missing attribute or interface. The handler class does not have the
#[AsCommandHandler]attribute or does not implementCommandHandler. - Type-hint mismatch. The handler's
__invoke()parameter type does not match the message class specified in the attribute. - Excluded from autoconfiguration. The handler class lives outside the
directory scanned by
config/services.yaml(e.g., a separate package not included in theresourceglob). - Wrong bus name. The attribute specifies a bus that does not match the
bundle configuration:
#[AsCommandHandler(bus: 'wrong.bus')].
Solution
- Verify the handler has both the attribute and the marker interface:
use SomeWork\CqrsBundle\Attribute\AsCommandHandler;
use SomeWork\CqrsBundle\Contract\CommandHandler;
#[AsCommandHandler(command: CreateTask::class)]
final class CreateTaskHandler implements CommandHandler
{
public function __invoke(CreateTask $command): mixed { /* ... */ }
}
-
Confirm the
__invoke()parameter type-hint matches the message class referenced in the attribute. -
Check that the handler's namespace is covered by your service configuration:
- Run the diagnostic command to confirm registration:
If the handler does not appear, the issue is in service discovery or attribute configuration.
2. Wrong bus routing
Symptom
A command or query is dispatched but the handler never executes, or the handler runs on an unexpected bus (visible in profiler or logs).
Cause
- Mismatched bus ID.
somework_cqrs.buses.commandpoints to a Messenger bus ID that differs from the one the handler is tagged for. - Handler on wrong bus. The handler attribute specifies bus A, but dispatch goes through bus B.
- Multiple bus configuration. When using multiple Messenger buses, the bus
names in handler attributes must match the bus IDs in
somework_cqrs.buses.*.
Solution
- Run
somework:cqrs:listwith details to see which bus each handler is on:
- Cross-reference the output with your bundle configuration:
somework_cqrs:
buses:
command: messenger.bus.commands # must match handler bus
query: messenger.bus.queries
event: messenger.bus.events
- Verify Messenger bus definitions match:
- If a handler specifies an explicit bus in its attribute, confirm it matches one of the configured bus IDs:
3. Async message dispatching synchronously
Symptom
A message configured for asynchronous dispatch runs in the same HTTP request (blocking). No message appears in the transport queue.
Cause
- Dispatch mode not configured. The
somework_cqrs.dispatch_modesconfig does not list the message class in itsmap, so the default (sync) applies. - Missing async bus.
somework_cqrs.buses.command_asyncis not set, so the bundle has no async bus to dispatch to. - Explicit sync override. The caller passes
DispatchMode::SYNCas the second argument todispatch(), overriding the config default. - Missing Messenger routing. Even with async dispatch mode, Messenger needs
a
framework.messenger.routingentry to route the message to a transport.
Solution
- Configure async dispatch for the message:
somework_cqrs:
dispatch_modes:
command:
default: sync
map:
App\Application\Command\GenerateReport: async
- Ensure the async bus is configured:
- Add Messenger transport routing:
- Verify transport mapping with the debug command:
- Check that caller code does not pass an explicit
DispatchMode::SYNC:
// Wrong -- forces sync even if config says async
$commandBus->dispatch($command, DispatchMode::SYNC);
// Correct -- respects config
$commandBus->dispatch($command);
4. Transport misconfiguration
Symptom
One of the following errors:
Runtime:
AsyncBusNotConfiguredException: Asynchronous command bus is not configured. Cannot dispatch "App\Application\Command\CreateTask" in async mode.
Compile time:
InvalidConfigurationException: Transport name "nonexistent_transport" is not defined in framework.messenger.transports.
Or messages are silently sent to the wrong transport.
Cause
- Async bus not set.
somework_cqrs.buses.command_asyncis null butdispatch_modes.command.defaultisasync, so the bundle cannot find an async bus to dispatch to. - Transport name mismatch. A transport name in
somework_cqrs.transports.*.mapdoes not match any entry inframework.messenger.transports. - Stamp class unavailable. Using
SendMessageToTransportsStampon Symfony versions before 6.3 where the stamp class does not exist.
Solution
- Set the async bus IDs for every type that uses async dispatch:
somework_cqrs:
buses:
command_async: messenger.bus.commands_async
event_async: messenger.bus.events_async
- Run the transport debug command to audit all transport mappings:
- Cross-reference transport names in bundle config with Messenger transports:
# Bundle config
somework_cqrs:
transports:
command_async:
default: ['async_commands']
# Must match a Messenger transport
framework:
messenger:
transports:
async_commands:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
- Check your Symfony version if using
SendMessageToTransportsStamp. The stamp requires Symfony 6.3 or later. For older versions, use the defaulttransport_namesstamp type.
Diagnostic commands
The bundle ships three console commands for inspecting your CQRS configuration:
| Command | Purpose | When to use |
|---|---|---|
somework:cqrs:list |
Lists all registered commands, queries, and events with handler metadata | Verify handlers are discovered and assigned to the correct bus |
somework:cqrs:debug-transports |
Inspects Messenger transport routing for CQRS messages | Diagnose transport name mismatches and async routing issues |
somework:cqrs:generate |
Scaffolds a message + handler skeleton | Bootstrap new messages with correct attribute and interface boilerplate |
Usage examples
# Show all handlers
bin/console somework:cqrs:list
# Filter by message type
bin/console somework:cqrs:list --type=command
# Show detailed handler info including bus assignments
bin/console somework:cqrs:list --details
# Audit transport routing
bin/console somework:cqrs:debug-transports
# Generate a new command with handler
bin/console somework:cqrs:generate command 'App\Application\Command\ShipOrder'