Связи между сущностями
У статьи есть комментарии, у комментария — автор, у статьи — метки. В Doctrine связь — это свойство, в котором лежит другой объект или коллекция объектов. Внешние ключи Doctrine создаёт и заполняет сам.
Создание связи
php bin/console make:entity CommentНа вопрос о типе поля ответьте relation — команда спросит, с какой сущностью связать и как, и допишет код в оба класса.
Многие к одному
У каждого комментария одна статья:
// 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;
}В таблице комментариев появится столбец post_id.
Один ко многим
Обратная сторона — коллекция комментариев у статьи:
// 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 удаляет комментарии вместе со статьёй.
Использование
$comment = new Comment();
$comment->setText('Отличная статья!');
$comment->setPost($post);
$em->persist($comment);
$em->flush();<h1>{{ post.title }}</h1>
{% for comment in post.comments %}
<p>{{ comment.text }}</p>
{% endfor %}Многие ко многим
У статьи много меток, у метки много статей. Промежуточную таблицу Doctrine создаст сам:
// 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;
}Проблема N+1
Связанные данные загружаются при первом обращении. Список из 50 статей с выводом автора каждой выполнит 51 запрос. Решение — соединить таблицы в одном запросе:
public function findAllWithAuthors(): array
{
return $this->createQueryBuilder('p')
->leftJoin('p.author', 'a')
->addSelect('a')
->getQuery()
->getResult();
}addSelect('a') загружает авторов сразу. Количество запросов видно в панели отладки.
Частая ошибка
В базу записывается только владеющая сторона связи — та, где стоит inversedBy (в паре «один ко многим» это всегда сторона ManyToOne). Если добавить комментарий в коллекцию $post->getComments()->add($comment) и не вызвать $comment->setPost($post), связь не сохранится. Методы addComment(), созданные командой make:entity, обновляют обе стороны — пользуйтесь ими.