DEV Community

Aymeric Ratinaud
Aymeric Ratinaud

Posted on

1

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

🚀

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

đź‘‹ Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay