默认情况下,Doctrine下的自引用ManyToMany关系涉及拥有侧和反向侧,如documentation中所述。

有没有办法实现互惠互利而双方之间没有分歧?

按照文档中的示例:

<?php
/** @Entity **/
class User
{
    // ...

    /**
     * @ManyToMany(targetEntity="User")
     **/
    private $friends;

    public function __construct() {
        $this->friends = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ...
}

因此,在entity1entity2中添加friends意味着entity2将在entity1的 friend 中。

最佳答案

有很多方法可以解决此问题,所有方法都取决于对“ friend ”关系的要求。

单向

一种简单的方法是使用单向的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中的friends-collection中,$userA将添加到$userB中的friends-collection中。

这还将导致2条记录添加到“friends”表中(1,2和2,1)。尽管这可以看作是重复数据,但是它将大大简化您的代码。例如,当您需要查找$userA的所有 friend 时,只需执行以下操作:
SELECT u FROM User u JOIN u.friends f WHERE f.id = :userId

无需像检查双向关联那样检查2个不同的属性。

双向

使用双向关联时,User实体将具有2个属性,例如$myFriends$friendsWithMe。您可以按照上述相同的方式使它们保持同步。

主要区别在于,在数据库级别上,您只有一条记录来表示关系(1,2或2,1)。这使“查找所有 friend ”查询更加复杂,因为您必须检查这两个属性。

您当然可以通过确保addFriend()将同时更新$myFriends$friendsWithMe(并使另一端保持同步)来仍然使用数据库中的2条记录。这将增加实体中的某些复杂性,但是查询变得稍微复杂一些。

OneToMany/ManyToOne

如果您需要一个系统,用户可以在其中添加 friend ,但该 friend 必须确认他们确实是 friend ,则需要将该确认存储在联接表中。然后,您将不再具有ManyToMany关联,而是类似User Friendship User

您可以阅读我有关此主题的博客文章:
  • Doctrine 2: How to handle join tables with extra columns
  • More on one-to-many/many-to-one associations in Doctrine 2
  • 10-06 09:41