web-dev-qa-db-ja.com

Doctrineの多対多の自己参照と相互関係

デフォルトでは、Doctrineの下での自己参照ManyToMany関係には、 ドキュメント で説明されているように、所有側と逆側が含まれます。

双方の違いなしに相互関連を実装する方法はありますか?

ドキュメントの例に従ってください:

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

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

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

    // ...
}

したがって、entity1entity2s friendsに追加すると、entity2entity1sの友達になります。

19
albertedevigo

この問題を解決する方法はいくつかありますが、すべて「友達」関係の要件によって異なります。

単方向

簡単なアプローチは、単方向の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つのレコード(1、2、2、1)が追加されます。これは重複データと見なすことができますが、コードが大幅に簡素化されます。たとえば、$userAのすべての友達を見つける必要がある場合は、次のようにするだけです。

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

双方向アソシエーションの場合のように、2つの異なるプロパティをチェックする必要はありません。

双方向

双方向の関連付けを使用する場合、Userエンティティには2つのプロパティがあります。たとえば$myFriends$friendsWithMeです。上記と同じ方法で同期を保つことができます。

主な違いは、データベースレベルでは、関係を表すレコードが1つしかないことです(1,2または2,1のいずれか)。これにより、両方のプロパティを確認する必要があるため、「すべての友達を検索」クエリが少し複雑になります。

もちろん、addFriend()$myFriends$friendsWithMeの両方を更新する(そして反対側の同期を維持する)ことを確認することで、データベース内の2つのレコードを使用することもできます。これにより、エンティティがいくらか複雑になりますが、クエリは少し複雑ではなくなります。

OneToMany/ManyToOne

ユーザーが友達を追加できるシステムが必要であるが、その友達が本当に友達であることを確認する必要がある場合は、その確認を結合テーブルに保存する必要があります。これで、ManyToManyの関連付けはなくなりますが、User <-OneToMany-> Friendship <-ManyToOne-> Userのようになります。

このテーマに関する私のブログ投稿を読むことができます:

49