Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

manyToMany causes duplicate entry error

I have an User entity. And those users can be friends together. So I defined a self referencing manyToMany unidirectional association (because there is always reciprocity is friendship, right ?).

a piece of my user entity in YML

manyToMany:
    friendList:
    targetEntity: User
    joinTable:
      name: user_friend
      joinColumns:
        user_id:
          referencedColumnName: id
      inverseJoinColumns:
        friend_id:
          referencedColumnName: id
    cascade: [persist]

When I call $user->addFriendList($friend), and after a persist and a flush, I have PDOException:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1-2' for fey 'PRIMARY'

When I check in the logs, I can see that doctrine is trying to exectue the same insert query twice.

For your information, my addFriendList function

public function addFriendList(User $friend)
{
    if (!$this->friendList->contains($friend)) {
        $this->friendList[] = $friend;
        $friend->addFriendList($this);
    }
}

Where am I wrong here ?

like image 563
Reuven Avatar asked Dec 14 '11 15:12

Reuven


3 Answers

I finally found a solution to my issue. However I still don't know if it's a Doctrine2 defect or if it works as designed.

I need to persist my user and flush before adding friends.

So my working code is:

$em->persist($user);
$em-flush();

$user->addFriendList($friend);
$em->persist($user);
$em->flush();
like image 152
Reuven Avatar answered Nov 18 '22 09:11

Reuven


@Reuven, you wrote :

$this->friendList[] = $friend;
$friend->addFriendList($this);

Well, you are inserting the relationship twice.

This should be enough :

$this->friendList[] = $friend;
like image 31
Daishi Avatar answered Nov 18 '22 09:11

Daishi


It's because you didn't specify an mappedBy (owning side) and inversedBy.

Check out this many-to-many relationship between Users and Roles:

/**
 * @ORM\Entity
 * @ORM\Table(name="user")
 */
class User
{

    /**
     * @ORM\ManyToMany(targetEntity="Role", mappedBy="users")
     * @ORM\JoinTable(name="user_role",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
     * )
     */
    protected $roles;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->roles = new ArrayCollection();
    }

    /**
     * Has role
     *
     * @param Role $role
     * @return bool
     */
    public function hasRole(Role $role)
    {
        return $this->roles->contains($role);
    }

    /**
     * Add role
     *
     * @param Role $role
     */
    public function addRole(Role $role)
    {
        if (!$this->hasRole($role)) {
            $this->roles->add($role);
            $role->addUser($this);
        }
    }

    /**
     * Remove roles
     *
     * @param Role $role
     */
    public function removeRole(Role $role)
    {
        if ($this->hasRole($role)) {
            $this->roles->removeElement($role);
            $role->removeUser($this);
        }
    }
}

.

/**
 * @ORM\Entity
 * @ORM\Table(name="role")
 */
class Role implements RoleInterface
{

    /**
     * @ORM\ManyToMany(targetEntity="User", inversedBy="roles")
     * @ORM\JoinTable(name="user_role",
     *      joinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}
     * )
     */
    private $users;

    public function __construct()
    {
        $this->users = new ArrayCollection();
    }

    /**
     * Has user
     *
     * @param User $user
     * @return bool
     */
    public function hasUser(User $user)
    {
        return $this->users->contains($user);
    }

    /**
     * Add user
     *
     * @param User $user
     */
    public function addUser(User $user)
    {
        if (!$this->hasUser($user)) {
            $this->users->add($user);
            $user->addRole($this);
        }
    }

    /**
     * Remove user
     *
     * @param User $user
     */
    public function removeUser(User $user)
    {
        if ($this->hasUser($user)) {
            $this->users->removeElement($user);
            $user->addRole($this);
        }
    }

    /**
     * Get users
     *
     * @return ArrayCollection
     */
    public function getUsers()
    {
        return $this->users;
    }
like image 1
Jonathan Avatar answered Nov 18 '22 08:11

Jonathan