Create a new Discussion with a Message and a Message to an existing Discussion [Api-platform]
Tags: api, symfony, php, apiplatform
Published: 2023-04-21 20:42:40

# 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

<?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
<?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:

```json
{
    "content": "Fuga ducimus debitis fuga quis sint similique dolores."
}
```

and not in this way

```json
{
    "messages": [
        "content": "Fuga ducimus debitis fuga quis sint similique dolores."
    ],
}
```

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

```php
#[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:

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

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

    return $this;
}
```

Now we can make a POST 

```json
{
    "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`

```json
{
    "content": "Hic ut et excepturi molestias amet sit."
}
```

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

```php
#[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
<?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 

```json
{
    "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

🚀