Доктрина «Многие-ко-многим» – саморегуляция и взаимность

По умолчанию, привязки ManyToMany соответствии с Doctrine связаны с собственностью и обратной стороной, как описано в документации .

Есть ли способ реализовать взаимную связь без различия между обеими сторонами?

Следуя примеру в документах:

 <?php /** @Entity **/ class User { // ... /** * @ManyToMany(targetEntity="User") **/ private $friends; public function __construct() { $this->friends = new \Doctrine\Common\Collections\ArrayCollection(); } // ... } 

Таким образом, добавление entity1 к entity2 означает, что entity2 будет находиться в entity1 .

Существует несколько способов решения этой проблемы, в зависимости от требований к отношениям «друзей».

однонаправленный

Простым подходом было бы использование однонаправленной ассоциации ManyToMany и относиться к ней так, как если бы она была двунаправленной (поддерживая обе стороны синхронно):

 /** * @Entity */ class User { /** * @Id * @Column(type="integer") */ private $id; /** * @ManyToMany(targetEntity="User") * @JoinTable(name="friends", * joinColumns={@JoinColumn(name="user_a_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="user_b_id", referencedColumnName="id")} * ) * @var \Doctrine\Common\Collections\ArrayCollection */ private $friends; /** * Constructor. */ public function __construct() { $this->friends = new \Doctrine\Common\Collections\ArrayCollection(); } /** * @return array */ public function getFriends() { return $this->friends->toArray(); } /** * @param User $user * @return void */ public function addFriend(User $user) { if (!$this->friends->contains($user)) { $this->friends->add($user); $user->addFriend($this); } } /** * @param User $user * @return void */ public function removeFriend(User $user) { if ($this->friends->contains($user)) { $this->friends->removeElement($user); $user->removeFriend($this); } } // ... } 

Когда вы вызываете $userA->addFriend($userB) , $userB будет добавлен в коллекцию друзей в $userA , а $userA будет добавлен в коллекцию друзей в $userB .

Это также приведет к добавлению 2 записей в таблицу «друзей» (1,2 и 2,1). Хотя это можно рассматривать как дубликаты данных, это значительно упростит ваш код. Например, когда вам нужно найти всех друзей $userA , вы можете просто сделать:

 SELECT u FROM User u JOIN u.friends f WHERE f.id = :userId 

Не нужно проверять 2 разных свойства, как с двунаправленной ассоциацией.

Двунаправленный

При использовании двунаправленной ассоциации User объект будет иметь 2 свойства, например $myFriends и $friendsWithMe . Вы можете синхронизировать их так же, как описано выше.

Основное различие заключается в том, что на уровне базы данных у вас будет только одна запись, представляющая отношение (1,2 или 2,1). Это делает запросы «найти всех друзей» более сложными, потому что вам нужно будет проверить оба свойства.

Конечно, вы могли бы использовать 2 записи в базе данных, убедившись, что addFriend() будет обновлять как $myFriends и $friendsWithMe (и синхронизировать другую сторону). Это добавит некоторую сложность в ваших сущностях, но запросы станут немного менее сложными.

OneToMany / ManyToOne

Если вам нужна система, в которой пользователь может добавить друга, но этот друг должен подтвердить, что они действительно друзья, вам нужно сохранить это подтверждение в таблице соединений. У вас больше нет ассоциации ManyToMany, но что-то вроде User <- OneToMany -> Friendship <- ManyToOne -> User .

Вы можете прочитать мои сообщения в блоге на эту тему:

  • Doctrine 2: Как обрабатывать таблицы объединений с дополнительными столбцами
  • Больше об ассоциациях «один-ко-многим» и «много-к-одному» в Доктрине 2