Doctrine's Many-To-Many Self-Referencing and reciprocity

后端 未结 1 969
我寻月下人不归
我寻月下人不归 2020-12-13 05:04

By default, self-referencing ManyToMany relationships under Doctrine involve an owning side and an inverse side, as explained in the documentation.

Is t

相关标签:
1条回答
  • 2020-12-13 05:19

    There are a number of ways to solve this problem, all depending on what the requirements for the "friends" relation are.

    Unidirectional

    A simple approach would be to use a unidirectional ManyToMany association, and treat it as if it where a bidirectional one (keeping both sides in sync):

    /**
     * @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);
            }
        }
    
        // ...
    
    }
    

    When you call $userA->addFriend($userB), $userB will be added to the friends-collection in $userA, and $userA will be added to the friends-collection in $userB.

    It will also result in 2 records added to the "friends" table (1,2 and 2,1). While this can be seen as duplicate data, it will simplify your code a lot. For example when you need to find all friends of $userA, you can simply do:

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

    No need to check 2 different properties as you would with a bidirectional association.

    Bidirectional

    When using a bidirectional association the User entity will have 2 properties, $myFriends and $friendsWithMe for example. You can keep them in sync the same way as described above.

    The main difference is that on a database level you'll only have one record representing the relationship (either 1,2 or 2,1). This makes "find all friends" queries a bit more complex because you'll have to check both properties.

    You could of course still use 2 records in the database by making sure addFriend() will update both $myFriends and $friendsWithMe (and keep the other side in sync). This will add some complexity in your entities, but queries become a little less complex.

    OneToMany / ManyToOne

    If you need a system where a user can add a friend, but that friend has to confirm that they are indeed friends, you'll need to store that confirmation in the join-table. You then no longer have a ManyToMany association, but something like User <- OneToMany -> Friendship <- ManyToOne -> User.

    You can read my blog-posts on this subject:

    • Doctrine 2: How to handle join tables with extra columns
    • More on one-to-many/many-to-one associations in Doctrine 2
    0 讨论(0)
提交回复
热议问题