with one many inversejoincolumns foreign fields collection php doctrine-orm doctrine many-to-many

php - one - symfony relation collection



La auto-referenciación de muchos a muchos y la reciprocidad de Doctrine (1)

De forma predeterminada, las relaciones de autorrecordación de ManyToMany en Doctrine implican un lado propietario y un lado inverso, como se explica en la documentation .

¿Hay alguna manera de implementar una asociación recíproca sin diferencias entre ambos lados?

Siguiendo el ejemplo en la documentación:

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

Por lo tanto, agregar entity1 a los friends entity1 implica que entity2 estará en amigos de entity1 .


Hay varias formas de resolver este problema, todas dependiendo de cuáles sean los requisitos para la relación de "amigos".

Unidireccional

Un enfoque simple sería utilizar una asociación ManyToMany unidireccional y tratarla como si fuera una bidireccional (manteniendo ambos lados sincronizados):

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

Cuando llame a $userA->addFriend($userB) , $userB se agregará a la colección de amigos en $userA , y $userA se agregará a la colección de amigos en $userB .

También resultará en 2 registros agregados a la tabla de "amigos" (1,2 y 2,1). Si bien esto puede verse como datos duplicados, simplificará mucho su código. Por ejemplo, cuando necesita encontrar a todos los amigos de $userA , simplemente puede hacer:

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

No es necesario verificar 2 propiedades diferentes como lo haría con una asociación bidireccional.

Bidireccional

Cuando se usa una asociación bidireccional, la entidad de User tendrá 2 propiedades, $myFriends y $friendsWithMe por ejemplo. Puede mantenerlos sincronizados de la misma manera que se describe anteriormente.

La principal diferencia es que, en el nivel de la base de datos, solo tendrá un registro que represente la relación (1,2 o 2,1). Esto hace que las consultas de "encontrar a todos los amigos" sean un poco más complejas porque tendrá que verificar ambas propiedades.

Por supuesto, aún podría usar 2 registros en la base de datos asegurándose de que addFriend() actualizará $myFriends y $friendsWithMe (y mantendrá sincronizado al otro lado). Esto agregará cierta complejidad en sus entidades, pero las consultas se vuelven un poco menos complejas.

OneToMany / ManyToOne

Si necesita un sistema en el que un usuario pueda agregar un amigo, pero ese amigo tiene que confirmar que realmente son amigos, deberá almacenar esa confirmación en la tabla de unión. Entonces ya no tienes una asociación ManyToMany, sino algo como User <- OneToMany -> Friendship <- ManyToOne -> User .

Puedes leer las publicaciones de mi blog sobre este tema: