ManyToMany Between Task and Tag
ManyToMany Between Task and Tag
~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
ONE task can have MULTIPLE tags, ONE tag can apply to MULTIPLE tasks – the CLASSIC many-to-many relationship, EXACTLY as in the Symfony course (chapter 32).
The tags property on Task
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
// ... inside the class:
#[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'tasks')]
#[Groups(['task:read', 'task:write'])]
private Collection $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
public function getTags(): Collection
{
return $this->tags;
}
public function addTag(Tag $tag): static
{
if (!$this->tags->contains($tag)) {
$this->tags->add($tag);
}
return $this;
}
public function removeTag(Tag $tag): static
{
$this->tags->removeElement($tag);
return $this;
}Achtung: NO set setter for tags, but add/remove instead – the Symfony Serializer AUTOMATICALLY recognizes this pattern and calls the matching add/remove methods WHEN deserializing a collection, EXACTLY as with a normal Symfony form.
The inverse side on Tag
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
// ... inside the class:
#[ORM\ManyToMany(targetEntity: Task::class, mappedBy: 'tags')]
private Collection $tasks;
public function __construct()
{
$this->tasks = new ArrayCollection();
}DELIBERATELY WITHOUT #[Groups] on tasks – Tag has been read-only since chapter 12 and doesn't NEED to show its related tasks in the API response to make the relationship USABLE.
Creating a migration
docker compose exec php bin/console make:migration
docker compose exec php bin/console doctrine:migrations:migrate --no-interactionCREATES an ADDITIONAL join table (task_tag) – EXACTLY the USUAL database pattern for many-to-many relationships, FULLY managed by Doctrine.
Creating a task with tags
curl -k -X POST https://localhost/api/tasks \
-H 'Content-Type: application/json' \
-d '{"title": "Prepare deployment", "project": "/api/projects/1", "tags": ["/api/tags/1", "/api/tags/2"]}'tags as an ARRAY of IRIs – EXACTLY the same principle as the SINGLE project field, just PLURAL for a many-to-many relationship.
Tipp: #[Assert\Count(min: 1)] from chapter 21 could be used HERE to enforce AT LEAST one tag per task – for OUR project, tags stay OPTIONAL.