3Cubes
3Cubes

Reputation: 13

Symfony Tactician-bundle Typehints = Missing handler method error

I've used the thephpleague/tactician-bundle with Symfony before, but this is the first time I've used it with Symfony 4.* (specifically 4.1.4) and attempted to use a single handler Class for my Application Service.

When I execute a command in the Controller

public function postAction(Request $request, CommandBus $commandBus)
{
    $form = $this->createForm(VenueType::class);
    $form->submit($request->request->all(), true);

    $data = $form->getData();

    if($form->isValid()) {
        $command = new CreateVenueCommand($data);
        $commandBus->handle($command);
        return $form->getData();
    }

    return $form;
}

... I get the following error:

"error": {
    "code": 500,
    "message": "Internal Server Error",
    "exception": [
        {
            "message": "Could not invoke handler for command App\\Application\\Command\\CreateVenueCommand for reason: Method 'handle' does not exist on handler",
            "class": "League\\Tactician\\Exception\\CanNotInvokeHandlerException",
            "trace": [

I've seemingly followed the installation documents for the tactician-bundle and installed it using Flex. As far as I can tell everything is configured correctly, so I'm unsure what I'm missing in my implementation.

Implementation

As per the thephpleague/tactician-bundle installation guide I've installed using Flex and the bundle is registered and the config package installed:

tactician:
    commandbus:
        default:
            middleware:
                - tactician.middleware.locking
                - tactician.middleware.doctrine
                - tactician.middleware.command_handler

After creating the DTO Command Class 'CreateVenueCommand', I created the handler Class:

use App\Infrastructure\Domain\Model\VenueRepositoryInterface;
use App\Application\Command\CreateVenueCommand;
use App\Domain\Entity\Venue;

class VenueApplicationService
{
    private $venueRepository;

    public function __construct(VenueRepositoryInterface $venueRepository)
    {
        $this->venueRepository = $venueRepository;
    }

    /**
     * @param CreateVenueCommand $aCommand
     * @throws \Exception
     */
    public function createVenue(CreateVenueCommand $aCommand)
    {
       $aVenue = new Venue($aCommand->getData())

        if ($aVenue === null) {
            throw new \LogicException('Venue not created');
        }

        $this->venueRepository->add($aVenue);

}

Then I registered the handler Class as a Service taking advantage of Symfony's autowiring and Tacticians typehints:

    App\Application\VenueApplicationService:
        arguments:
            - '@App\Infrastructure\Persistence\Doctrine\DoctrineVenueRepository'
        tags:
           - { name: tactician.handler, typehints: true }

So according to the installation documents, typehints work if:

  1. The method must be public.
  2. The method must accept only one parameter.
  3. The parameter must be typehinted with a class name.

Also, and this is specific to my use case:

If you have multiple commands going into a single handler, they will all be detected, provided they follow the rules above. The actual name of the method is NOT important.

So when I invoke the commandbus in the Controller Class, I'm unsure why I'm getting the error above.

If I change the Command Handler method to:

public function handle(CreateVenueCommand $aCommand)
{

... then it works fine. This would seem to suggest that the typehints aren't working as documented.

It seems in this case that the actual name of the method IS important. ... or I've made some form of error in my implementation ... or I'm misunderstanding the multiple commands going into a single handler use case??

Any assistance would be greatly appreciated.

Solution

With a big thanks to kunicmarko20 for pointing me in the right direction.

Specifically for my use case I simply needed to use one of Tacticians MethodNameInflector classes, configured in Symfony thus:

tactician:
    commandbus:
        default:
            middleware:
                - tactician.middleware.locking
                - tactician.middleware.doctrine
                - tactician.middleware.command_handler
            method_inflector: tactician.handler.method_name_inflector.handle_class_name

... then it was simply a matter of naming each Handler method in my Application Service class 'handle{whateverYouLike}Command

Upvotes: 1

Views: 1424

Answers (1)

kunicmarko20
kunicmarko20

Reputation: 2180

Here under 1. is explained how the naming works, if you want to use a different name than in this table you can implement MethodNameInflector Interface and provide a name of the method.

Upvotes: 1

Related Questions