Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Связи между сущностями

У статьи есть комментарии, у комментария — автор, у статьи — метки. В 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, обновляют обе стороны — пользуйтесь ими.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.