Skip to content

Associations & N+1 ​

Relations are the same story as FE nested data - fetch carefully or you'll N+1 yourself into sadness.

ManyToOne / OneToMany ​

php
<?php

// Post.php
#[ORM\ManyToOne(inversedBy: 'posts')]
#[ORM\JoinColumn(nullable: false)]
private ?User $author = null;

// User.php
/** @var Collection<int, Post> */
#[ORM\OneToMany(mappedBy: 'author', cascade: ['persist'], orphanRemoval: true)]
private Collection $posts;

Comments for the code above:

  • Owning side has the FK (ManyToOne on Post)
  • Inverse side (OneToMany) is for navigating $user->getPosts()
  • MakerBundle (make:entity relations) writes a lot of this boilerplate for you

ManyToMany ​

Tags, roles, weird junction tables - Doctrine can own the join table via ManyToMany. Start with ManyToOne until you need it.

The N+1 problem ​

php
<?php

$posts = $posts->findAll();
foreach ($posts as $post) {
    echo $post->getAuthor()->getEmail(); // extra query per post if lazy
}

Doctrine lazy-loads associations by default (proxies). List pages need joins / eager fetch.

Fix with QueryBuilder ​

php
<?php

return $this->createQueryBuilder('p')
    ->addSelect('a')
    ->join('p.author', 'a')
    ->orderBy('p.id', 'DESC')
    ->getQuery()
    ->getResult();

Or attribute fetch hints / Criteria later - the habit is: list endpoints decide their graph.

Profiler is your friend ​

Open the Doctrine panel in the web debug toolbar. If you see 1 + N identical SELECTs - you found it. Same instinct as React Query waterfall debugging, honestly.

Next: Security - firewalls, users, passwords.

Best,

-- ł.

Continues after /lab/php-tutorial-for-fe-devs/