I have this command
<?php
namespace App\Todo\Application\Command;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
class CreateTodoCommand
{
public function __construct(
private string $name,
private string $text,
private string $userId
) { }
public function getName(): string
{
return $this->name;
}
public function getText(): string
{
return $this->text;
}
public function getUserId(): UuidInterface
{
return Uuid::fromString($this->userId);
}
}
and this handler
<?php
namespace App\Todo\Application\Command;
use App\Todo\Application\Interfaces\CommandHandlerInterface;
use App\Todo\Domain\Entity\Todo;
use App\Todo\Domain\Repository\TodoRepositoryInterface;
use App\Todo\Domain\ValueObject\Name;
use App\Todo\Domain\ValueObject\Text;
use App\Todo\Domain\ValueObject\UserId;
use Ramsey\Uuid\Uuid;
class CreateTodoCommandHandler implements CommandHandlerInterface
{
public function __construct(private TodoRepositoryInterface $todoRepository)
{}
public function __invoke(CreateTodoCommand $command)
{
$todo = new Todo(
Uuid::uuid4(),
new Name($command->getName()),
new Text($command->getTe xt()),
new UserId($command->getUserId())
);
$this->todoRepository->save($todo);
}
}
this messenger.yaml
configuration
framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(RABBITMQ_DSN)%'
retry_strategy:
max_retries: 5
delay: 1000
multiplier: 2
max_delay: 60000
# failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
# Route your messages to the transports
# 'App\Message\YourMessage': async
'App\Todo\Application\Command\CreateTodoCommand': async
and this services.yaml
config
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# Registra el repositorio como servicio
App\Todo\Infrastructure\Repository\DoctrineTodoRepository:
arguments:
$em: '@doctrine.orm.entity_manager'
# Registra el handler y autowire la interfaz con su implementación
App\Todo\Application\Command\CreateTodoCommandHandler:
arguments:
$todoRepository: '@App\Todo\Infrastructure\Repository\DoctrineTodoRepository'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
But when I try t consume the queued messages I get this error
messenger.CRITICAL: Error thrown while handling message App\Todo\Application\Command\CreateTodoCommand. Removing from transport after 5 retries. Error: "No handler for message "App\Todo\Application\Command\CreateTodoCommand"." {"class":"App\\Todo\\Application\\Command\\CreateTodoCommand","retryCount":5,"error":"No handler for message \"App\\Todo\\Application\\Command\\CreateTodoCommand\".","exception":"[object] (Symfony\\Component\\Messenger\\Exception\\NoHandlerForMessageException(code: 0): No handler for message \"App\\Todo\\Application\\Command\\CreateTodoCommand\". at /var/www/html/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:117)"} []
If I check the handlers with bin/console debug:messenger
there is no CreateTodoCommandHandler
on the list
Messenger
=========
messenger.bus.default
---------------------
The following messages can be dispatched:
----------------------------------------------------------
Symfony\Component\Process\Messenger\RunProcessMessage
handled by process.messenger.process_message_handler
Symfony\Component\Console\Messenger\RunCommandMessage
handled by console.messenger.execute_command_handler
Symfony\Component\Messenger\Message\RedispatchMessage
handled by messenger.redispatch_message_handler
----------------------------------------------------------
And I cleared cache several times
I think the config is correct, but there is no way to set the handler, there is something wrong or do I missed something?
If I use an attribute
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class CreateTodoCommandHandler implements CommandHandlerInterface
then everything work correctly.
It's a handler registration problem, and I need to set the handler without using an attribute.
Since you do not want to use configuration attributes, you'll need to provide the configuration manually.
E.g.:
# config/services.yaml
services:
# this config only applies to the services created by this file
_instanceof:
# services whose classes are instances of CommandHandlerInterface will be tagged automatically
App\Todo\Application\Interfaces\CommandHandlerInterface:
tags: ['messenger.message_handler' { bus: 'command.bus' }]
# ...
With the above, any class that implements CommandHandlerInterface
will be tagged 'messenger.message_handler'
, which in turn will cause the service to be registered as a message handler for Messenger, and be directed to use the command
bus.
You may need to perform some application specific adjustments to your own use-case, but this should take you in the right direction.
Docs: