DEV Community

Aymeric Ratinaud
Aymeric Ratinaud

Posted on

Create a new Discussion with a Message and a Message to an existing Discussion [Api-platform]

Introduction

We will create two POST routes, one to create a Discussion with a Message and a second to add a Message to that Discussion. Both POSTs will have the same request body.

The interest of this article is to show you how to define your entity to wait for a Message (while it is a OneToMany relationship and should therefore wait for an array of messages).

Then we will create a route that will indicate the Discussion on which we will add a new Message

Create our Message entity


<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\Repository\MessageRepository;
use App\State\MessagePostProcessor;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: MessageRepository::class)]
#[ApiResource(
    normalizationContext: [
        'groups' => ['message:read']
    ],
    denormalizationContext: [
        'groups' => ['message:write']
    ],
)]
class Message
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(type: Types::TEXT)]
    #[Groups(groups: ['message:read', 'message:write'])]
    #[Assert\NotBlank()]
    private ?string $content = null;

    #[ORM\ManyToOne(inversedBy: 'messages')]
    #[ORM\JoinColumn(nullable: false)]
    private ?Discussion $discussion = null;

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

    public function getContent(): ?string
    {
        return $this->content;
    }

    public function setContent(string $content): self
    {
        $this->content = $content;

        return $this;
    }

    public function getDiscussion(): ?Discussion
    {
        return $this->discussion;
    }

    public function setDiscussion(?Discussion $discussion): self
    {
        $this->discussion = $discussion;

        return $this;
    }
}
Enter fullscreen mode Exit fullscreen mode

Our Discussion entity

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\DiscussionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: DiscussionRepository::class)]
#[ApiResource(
    normalizationContext: [
        'groups' => ['discussion:read']
    ],
    denormalizationContext: [
        'groups' => ['discussion:write']
    ],
)]
class Discussion 
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\OneToMany(mappedBy: 'discussion', targetEntity: Message::class, cascade: ['persist'], orphanRemoval: true)]
    private Collection $messages;

    public function __construct()
    {
        $this->messages = new ArrayCollection();
    }

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

    /**
     * @return Collection<int, Message>
     */
    public function getMessages(): Collection
    {
        return $this->messages;
    }

    public function addMessage(Message $message): self
    {
        if (!$this->messages->contains($message)) {
            $this->messages->add($message);
            $message->setDiscussion($this);
        }

        return $this;
    }

    public function removeMessage(Message $message): self
    {
        if ($this->messages->removeElement($message)) {
            // set the owning side to null (unless already changed)
            if ($message->getDiscussion() === $this) {
                $message->setDiscussion(null);
            }
        }

        return $this;
    }

}
Enter fullscreen mode Exit fullscreen mode

First part - Create a new Discussion with a Message

Now we would like that on the POST /api/discussions we can send the content of the message this way:

{
    "content": "Fuga ducimus debitis fuga quis sint similique dolores."
}
Enter fullscreen mode Exit fullscreen mode

and not in this way

{
    "messages": [
        "content": "Fuga ducimus debitis fuga quis sint similique dolores."
    ],
}
Enter fullscreen mode Exit fullscreen mode

We will create an attribute on Discussion that will not be persisted.

#[Groups(groups: ['discussion:write'])]
private string $content;
Enter fullscreen mode Exit fullscreen mode

With the discussion:write group, the setter setContent will be called and the message will be added with the method addMessage which is already present. This is the setter:

public function setContent(string $content): self
{
    $message = new Message();

    $message->setContent($content);
    $this->addMessage($message);

    return $this;
}
Enter fullscreen mode Exit fullscreen mode

Now we can make a POST

{
    "content": "Fuga ducimus debitis fuga quis sint similique dolores."
}
Enter fullscreen mode Exit fullscreen mode

which create a Discussion and its related Message.

Second part - Create a new Message to an existing Discussion

Now we will define the following route, to retrieve the discussion and add a new message on it.

POST /api/discussions/{id}/message

{
    "content": "Hic ut et excepturi molestias amet sit."
}
Enter fullscreen mode Exit fullscreen mode

To define the new route we are in the Message entity:

#[ORM\Entity(repositoryClass: MessageRepository::class)]
#[ApiResource(
    normalizationContext: [
        'groups' => ['message:read']
    ],
    denormalizationContext: [
        'groups' => ['message:write']
    ],
)]
#[Post(
    uriTemplate: '/discussions/{id}/message',
    read: false,
    processor: MessagePostProcessor::class
)]
class Message {
// ...
Enter fullscreen mode Exit fullscreen mode

We need to create the MessagePostProcessor:

<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Message;
use App\Repository\DiscussionRepository;
use Doctrine\ORM\EntityManagerInterface;

class MessagePostProcessor implements ProcessorInterface
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly DiscussionRepository $discussionRepository
    ) {
    }

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
    {
        $discussion = $this->discussionRepository->find($uriVariables['id']);

        $discussion->addMessage($data);

        $this->entityManager->persist($data);
        $this->entityManager->flush();
    }
}
Enter fullscreen mode Exit fullscreen mode

This Processor gets the id of the Discussion with the variable uriVariables['id'] and then Message that is contained in $data is added to this discussion, and then persisted.

We can now make a POST to POST /api/discussions/{id}/message with the body

{
    "content": "Hic ut et excepturi molestias amet sit."
}
Enter fullscreen mode Exit fullscreen mode

You can consult the implementation of this article on this commit: https://github.com/aratinau/api-platform3/commit/cc3cb8afb7331b07309565d66f169a106c92f349

🚀

Top comments (0)