How to delete from a JoinTable in a Doctrine ManyToMany association

Doctrine: how to delete records from a table in a ManyToMany association from the inverse side

In a ManyToMany association there is always an owner side where you define the JoinTable and if you want to remove from the join table, not the entities, a simple cascade remove will not do it.

You need to implement a remove method with your array collection as Doctrine does not go that far.

In this case we are to see the well known user_group sample. We want to delete from user_group but not delete user or  group.

The owner side

     class User
     {
     
    /**
     * @var int|null
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer", name="id")
     */
    protected $id;
    
    /**
     * @var \Doctrine\Common\Collections\Collection|UserGroup[]
     *
     * @ORM\ManyToMany(targetEntity="UserGroup", inversedBy="users")
     * @ORM\JoinTable(
     *  name="user_group",
     *  joinColumns={
     *      @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     *  },
     *  inverseJoinColumns={
     *      @ORM\JoinColumn(name="usergroup_id", referencedColumnName="id")
     *  }
     * )
     */
    protected $userGroups;

/**
* @param UserGroup $userGroup
*/
public function addUserGroup(UserGroup $userGroup)
{
if ($this->userGroups->contains($userGroup)) {
return;
}
$this->userGroups->add($userGroup);
// From the other side
$userGroup->addUser($this);
} /** * @param UserGroup $userGroup */ public function removeUserGroup(UserGroup $userGroup) { if (!$this->userGroups->contains($userGroup)) { return; } $this->userGroups->removeElement($userGroup); $userGroup->removeUser($this); } }

We could say that the owner side could be on any of the two entities, but using some logic we could just say that a group is made of users and not the other way. Sot let user be the owner side.

For this sample we have implemented the remove and addUserGroup methods.

The inverse side

The inverse side holds the variable $users:


class UserGroup { /** * @var int|null * @ORM\Id() * @ORM\GeneratedValue(strategy="AUTO") * @ORM\Column(type="integer", name="id") */ protected $id; /** * @var \Doctrine\Common\Collections\Collection|User[] * * @ORM\ManyToMany(targetEntity="User", mappedBy="userGroups") */ protected $users; /** * Default constructor, initializes collections */ public function __construct() { $this->users = new ArrayCollection(); }
/**
* @param User $user
*/
public function addUser(User $user)
{
if ($this->users->contains($user)) {
return;
}
$this->users->add($user);
$user->addUserGroup($this);
}
/** * @param User $user */ public function removeUser(User $user) { if (!$this->users->contains($user)) { return; } $this->users->removeElement($user); $user->removeUserGroup($this); } /** * Get Users * * @return ArrayCollection[User] */ public function getUsers() { return $this->users; } }

Remove elements

Then to remove elements from the inverse side $userGroup it could be done like this:

foreach($userGroup->getUsers() as $user) {
        $userGroup->removeUser($user);
}
        

That code snippet could be placed in a Listener that could be invoked on a preRemove event, so it would be nicely done by itself each time you invoke a remove on the UserGroup entity.