Назад к вопросам
Middle+
108
questionbank

Как в Doctrine реализовать отношение 'has many through'?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

В Doctrine2 отношение "has many through" реализуется через явное создание промежуточной сущности (join entity), которая связывает две другие сущности отношениями Many-to-One.

  1. Создается три класса сущностей: Источниковая, Целевая и Промежуточная (JoinEntity).

  2. Промежуточная сущность (например, UserGroup для "User has many Group through UserGroup") содержит два поля, которые являются отношениями Many-to-One к Источниковой (User) и Целевой (Group) сущностям.

  3. Источниковая и Целевая сущности имеют отношение One-to-Many к Промежуточной сущности.

  4. Для удобства доступа к Целевым сущностям через Источниковую (например, получить все Group для User), можно добавить метод в Источниковую сущность, который будет фильтровать Промежуточные сущности.

Пример структуры:

// User.php
<?php

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\OneToMany(targetEntity: UserGroup::class, mappedBy: 'user')]
    private Collection $userGroups; // Отношение к промежуточной сущности

    public function __construct()
    {
        $this->userGroups = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * Получение групп пользователя через промежуточную сущность.
     * This is the equivalent of "has many through".
     * Note: You might want to fetch this collection efficiently depending on your needs.
     */
    public function getGroups(): Collection
    {
        return $this->userGroups->map(fn(UserGroup $userGroup) => $userGroup->getGroup());
    }

    public function getUserGroups(): Collection
    {
        return $this->userGroups;
    }

    public function addUserGroup(UserGroup $userGroup): static
    {
        if (!$this->userGroups->contains($userGroup)) {
            $this->userGroups->add($userGroup);
            $userGroup->setUser($this);
        }

        return $this;
    }

    public function removeUserGroup(UserGroup $userGroup): static
    {
        if ($this->userGroups->removeElement($userGroup)) {
            // set the owning side to null (unless already changed)
            if ($userGroup->getUser() === $this) {
                $userGroup->setUser(null);
            }
        }

        return $this;
    }
}
// Group.php
<?php

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Group
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\OneToMany(targetEntity: UserGroup::class, mappedBy: 'group')]
    private Collection $userGroups; // Отношение к промежуточной сущности

    public function __construct()
    {
        $this->userGroups = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUserGroups(): Collection
    {
        return $this->userGroups;
    }

    public function addUserGroup(UserGroup $userGroup): static
    {
        if (!$this->userGroups->contains($userGroup)) {
            $this->userGroups->add($userGroup);
            $userGroup->setGroup($this);
        }

        return $this;
    }

    public function removeUserGroup(UserGroup $userGroup): static
    {
        if ($this->userGroups->removeElement($userGroup)) {
            // set the owning side to null (unless already changed)
            if ($userGroup->getGroup() === $this) {
                $userGroup->setGroup(null);
            }
        }

        return $this;
    }
}
// UserGroup.php
<?php

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'user_user_group')] // Пример имени таблицы
class UserGroup
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'userGroups')]
    #[ORM\JoinColumn(nullable: false)]
    private ?User $user = null; // Many-to-One к User

    #[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'userGroups')]
    #[ORM\JoinColumn(nullable: false)]
    private ?Group $group = null; // Many-to-One к Group

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    public function setUser(?User $user): static
    {
        $this->user = $user;

        return $this;
    }

    public function getGroup(): ?Group
    {
        return $this->group;
    }

    public function setGroup(?Group $group): static
    {
        $this->group = $group;

        return $this;
    }
}

Этот подход, хотя и требует создания дополнительной сущности, дает полный контроль над данными связей и позволяет легко добавлять дополнительные поля в промежуточную таблицу (например, дата присоединения пользователя к группе).