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 (
ManyToOneon Post) - Inverse side (
OneToMany) is for navigating$user->getPosts() - MakerBundle (
make:entityrelations) 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,
-- ł.