Core: Messenger component does not work with PUT, DELETE, GET

Created on 21 Sep 2019  路  6Comments  路  Source: api-platform/core

Hi! I want to use Message component with PUT\PATCH, DELETE and GET but as I understand the message component work only with POST method, am I right? For example

/**
 * @ApiResource(
 *     shortName="Club",
 *     routePrefix="crm",
 *     messenger="input",
 *     collectionOperations={
 *          "get",
 *          "post"={"path"="/clubs", "input"=CreateClubCommand::class},
 *     },
 *     itemOperations={
 *          "get"={"path"="/clubs/{id}", "input"=FindClubByNameQueryItem::class},
 *          "put"={"path"="/clubs/{id}", "input"=UpdateClubCommand::class}
 *     },
 *     normalizationContext={"groups"={"club_item", "club_list"}},
 *     denormalizationContext={"groups"={"club_write"}}
 * )
 */
final class ClubResource

In this case, I expect that all input DTO's (commands\queries) will be passed to their handlers as in POST case. Is it possible to do without custom code? How do you see it's better to use message component for other request methods e.g. GET, PUT, DELETE to implement CQS or CQRS?

Hacktoberfest easy pick

Most helpful comment

DELETE is already supported. However, it would be great to be able to detect in the handler what was the original HTTP verb.
My suggestion is to create a new ContextStamp containing the content of the $context parameter of DataPersister::persist() and DataPersister::remove(). As already done for remove: https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Messenger/DataPersister.php#L94

It would allow to access the whole context (including the HTTP verb used), and I'm sure that it will be useful to many!

All 6 comments

The fact is that input data is given by the user when creating the request, because "read" routes are not denormalizing data they won't work with input classes, indeed they're queries. I don't really understand the need to use messenger for read routes, or anything that does not need asynchronous processing.

@soyuka Hi! Thank you for your response.
I tested today POST and PUT again and noticed that PUT works well with message component, I guess previously it was a cache problem and in my case PUT Command did not pass to the handler.

I agree with you at some point about GET operations, but what about DELETE. DELETE is a mutator and in my opinion, should work in the same way as other mutators (POST or PUT).

DELETE is already supported. However, it would be great to be able to detect in the handler what was the original HTTP verb.
My suggestion is to create a new ContextStamp containing the content of the $context parameter of DataPersister::persist() and DataPersister::remove(). As already done for remove: https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Messenger/DataPersister.php#L94

It would allow to access the whole context (including the HTTP verb used), and I'm sure that it will be useful to many!

Is this still an issue. I'm likely to want to use this use case imminently in my project. Happy to work on a PR if need be, if someone can advise on what is still outstanding on this. The comments on https://github.com/api-platform/core/pull/3157#issuecomment-543274766 don't make it clear what's still required on this and it's over a year old, so I guess a lot has changed since this was last discussed.

Hello everyone!

Symfony Messenger still doesn't support the ability to implement what the author wanted? I have a similar problem:

Symfony 5.2 with api-platform 2.5:

// src/Entity/User.php

 * @ApiResource(
 *     messenger="input",
 *     collectionOperations={
 *         "get",
 *         "post"={"status"=202, "input"=CreateUserCommand::class, "output"=false}
 *     },
 *     itemOperations={
 *         "put"={"status"=202, "input"=UpdateUserCommand::class, "output"=false},
 *         "delete"={"status"=202, "input"=DeleteUserCommand::class, "output"=false}
 *     }
 * )
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\Column(type="ulid", unique=true)
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $email;

    public function getId(): ?Ulid
    {
        return $this->id;
    }

    public function setId(Ulid $id): self
    {
        $this->id = $id;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }
}

// src/MessageHandler/CommandHandlerInterface.php

interface CommandHandlerInterface
{
}

// config.servises.yaml

...
    _instanceof:
        # all services implementing the CommandHandlerInterface
        # will be registered on the command.bus bus
        App\MessageHandler\CommandHandlerInterface:
            tags:
                - { name: messenger.message_handler, bus: command.bus }

// src/Message/Command/CreateUserCommand.php

final class CreateUserCommand
{
    /**
     * @Assert\NotBlank
     * @Assert\Ulid(
     *     message = "The id {{ value }} is not a valid Ulid."
     * )
     */
    private $id;

    /**
     * @Assert\NotBlank
     * @Assert\Email(
     *     message = "The email {{ value }} is not a valid email."
     * )
     */
    private $email;
}

// src/MessageHandler/Command/CreateUserCommand.php

final class CreateUserCommandHandler implements CommandHandlerInterface
{
    private $entityManager;

    public function __construct(
        EntityManagerInterface $entityManager,
    )
    {
        $this->entityManager = $entityManager;
    }

    public function __invoke(CreateUserCommand $createUserCommand)
    {
        $user = new User();

        /* @var $id Ulid */
        $id = Ulid::fromString($createUserCommand->getId());

        $user->setId($id);
        $user->setEmail($createUserCommand->getEmail());

        $this->entityManager->persist($user);
        $this->entityManager->flush();
    }
}

CreateUserCommand - OK

I want to implement an update:

// src/Message/Command/UpdateUserCommand.php

final class UpdateUserCommand
{
    /**
     * @Assert\NotBlank
     * @Assert\Email(
     *     message = "The email {{ value }} is not a valid email."
     * )
     */
    private $email;

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): string
    {
        return $this->email = $email;
    }
}

// src/MessageHandler/Command/UpdateUserCommand.php

final class UpdateUserCommandHandler implements CommandHandlerInterface
{
    private $entityManager;
    private $userRepository;

    public function __construct(
        EntityManagerInterface $entityManager,
        UserRepository $userRepository,
    )
    {
        $this->entityManager = $entityManager;
        $this->userRepository = $userRepository;
    }

    public function __invoke(UpdateUserCommand $updateUserCommand)
    {
        // TODO:: How can I get the ID of the resource that is passed in the URL?
    }
}

Unfortunately, I could not find a solution to this problem in open sources. Perhaps someone faced a similar task?

@mxkh, did you manage to find answers to your questions?

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vViktorPL picture vViktorPL  路  3Comments

mahmoodbazdar picture mahmoodbazdar  路  3Comments

theshaunwalker picture theshaunwalker  路  3Comments

soyuka picture soyuka  路  3Comments

stipic picture stipic  路  3Comments