Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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 Comment

When 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.

Thanks for reading!

Donate to the site — we work hard for you.