By default, self-referencing ManyToMany
relationships under Doctrine involve an owning side and an inverse side, as explained in the documentation.
默认情况下,Doctrine下的自引用ManyToMany关系涉及拥有方和反方,如文档中所述。
Is there a way to implement a reciprocal association whithout difference between both sides?
有没有办法实现双方之间没有差异的互惠协会?
Following the example in the docs:
按照文档中的示例:
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="User")
**/
private $friends;
public function __construct() {
$this->friends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
So, adding entity1
to entity2
s friends
implies that entity2
will be in entity1
s friends.
因此,将entity1添加到entity2s的朋友意味着entity2将在entity1s的朋友中。
1 个解决方案
#1
46
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):
一种简单的方法是使用单向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);
}
}
// ...
}
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
.
当你拨打$ userA-> addFriend($ userB)时,$ userB将被添加到$ userA中的friends-collection,$ userA将被添加到$ userB中的friends-collection。
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:
它还会将2条记录添加到“朋友”表(1,2和2,1)中。虽然这可以看作是重复数据,但它会大大简化您的代码。例如,当您需要找到$ userA的所有朋友时,您可以简单地执行以下操作:
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.
无需像使用双向关联那样检查2个不同的属性。
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.
使用双向关联时,User实体将具有2个属性,例如$ myFriends和$ friendsWithMe。您可以按照与上述相同的方式使它们保持同步。
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.
主要区别在于,在数据库级别上,您只有一条记录代表关系(1,2或2,1)。这使得“查找所有朋友”查询更加复杂,因为您必须检查这两个属性。
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.
您当然可以通过确保addFriend()更新$ myFriends和$ friendsWithMe(并保持另一方同步)来在数据库中使用2条记录。这将在您的实体中增加一些复杂性,但查询变得不那么复杂。
OneToMany / ManyToOne
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
.
如果您需要一个系统,用户可以添加朋友,但该朋友必须确认他们确实是朋友,您需要将该确认存储在连接表中。然后,您不再拥有ManyToMany关联,而是User < - OneToMany - > Friendship < - ManyToOne - > User。
You can read my blog-posts on this subject:
你可以阅读我关于这个主题的博客文章:
- Doctrine 2: How to handle join tables with extra columns
- Doctrine 2:如何处理带有额外列的连接表
- More on one-to-many/many-to-one associations in Doctrine 2
- 更多关于Doctrine 2中的一对多/多对一关联
#1
46
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):
一种简单的方法是使用单向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);
}
}
// ...
}
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
.
当你拨打$ userA-> addFriend($ userB)时,$ userB将被添加到$ userA中的friends-collection,$ userA将被添加到$ userB中的friends-collection。
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:
它还会将2条记录添加到“朋友”表(1,2和2,1)中。虽然这可以看作是重复数据,但它会大大简化您的代码。例如,当您需要找到$ userA的所有朋友时,您可以简单地执行以下操作:
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.
无需像使用双向关联那样检查2个不同的属性。
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.
使用双向关联时,User实体将具有2个属性,例如$ myFriends和$ friendsWithMe。您可以按照与上述相同的方式使它们保持同步。
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.
主要区别在于,在数据库级别上,您只有一条记录代表关系(1,2或2,1)。这使得“查找所有朋友”查询更加复杂,因为您必须检查这两个属性。
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.
您当然可以通过确保addFriend()更新$ myFriends和$ friendsWithMe(并保持另一方同步)来在数据库中使用2条记录。这将在您的实体中增加一些复杂性,但查询变得不那么复杂。
OneToMany / ManyToOne
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
.
如果您需要一个系统,用户可以添加朋友,但该朋友必须确认他们确实是朋友,您需要将该确认存储在连接表中。然后,您不再拥有ManyToMany关联,而是User < - OneToMany - > Friendship < - ManyToOne - > User。
You can read my blog-posts on this subject:
你可以阅读我关于这个主题的博客文章:
- Doctrine 2: How to handle join tables with extra columns
- Doctrine 2:如何处理带有额外列的连接表
- More on one-to-many/many-to-one associations in Doctrine 2
- 更多关于Doctrine 2中的一对多/多对一关联