Relationships between entities
A post has comments, a comment has an author, a post has tags. In Doctrine a relationship is a property holding another object or a collection of objects. Doctrine creates and fills foreign keys by itself.
Creating a relationship
php bin/console make:entity CommentWhen asked for the field type, answer relation — the command asks which entity to relate and how, and adds code to both classes.
Many to one
Every comment has one post:
// src/Entity/Comment.php
#[ORM\ManyToOne(inversedBy: 'comments')]
#[ORM\JoinColumn(nullable: false)]
private ?Post $post = null;
public function setPost(?Post $post): static
{
$this->post = $post;
return $this;
}A post_id column appears in the comments table.
One to many
The inverse side is the post's collection of comments:
// src/Entity/Post.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'post', orphanRemoval: true)]
private Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getComments(): Collection
{
return $this->comments;
}orphanRemoval: true deletes the comments together with the post.
Using it
$comment = new Comment();
$comment->setText('Great post!');
$comment->setPost($post);
$em->persist($comment);
$em->flush();<h1>{{ post.title }}</h1>
{% for comment in post.comments %}
<p>{{ comment.text }}</p>
{% endfor %}Many to many
A post has many tags, a tag has many posts. Doctrine creates the join table by itself:
// src/Entity/Post.php
#[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'posts')]
private Collection $tags;
public function addTag(Tag $tag): static
{
if (!$this->tags->contains($tag)) {
$this->tags->add($tag);
}
return $this;
}The N+1 problem
Related data is loaded on first access. A list of 50 posts printing each author runs 51 queries. The fix is to join the tables in one query:
public function findAllWithAuthors(): array
{
return $this->createQueryBuilder('p')
->leftJoin('p.author', 'a')
->addSelect('a')
->getQuery()
->getResult();
}addSelect('a') loads the authors right away. The number of queries is visible in the debug toolbar.
Common mistake
Only the owning side of a relationship is written to the database — the one with inversedBy (in a one-to-many pair that is always the ManyToOne side). If you add a comment to the collection with $post->getComments()->add($comment) and do not call $comment->setPost($post), the relationship is not saved. The addComment() methods generated by make:entity update both sides — use them.