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;
}
}
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;
}
}
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."
}
and not in this way
{
"messages": [
"content": "Fuga ducimus debitis fuga quis sint similique dolores."
],
}
We will create an attribute on Discussion that will not be persisted.
#[Groups(groups: ['discussion:write'])]
private string $content;
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;
}
Now we can make a POST
{
"content": "Fuga ducimus debitis fuga quis sint similique dolores."
}
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."
}
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 {
// ...
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();
}
}
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."
}
You can consult the implementation of this article on this commit: https://github.com/aratinau/api-platform3/commit/cc3cb8afb7331b07309565d66f169a106c92f349
🚀
Top comments (0)