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)