0

I have a classical construction like EntityA OneToMany EntityB. Implemented as a bidirectional relationship:

  • the EntityB has a property $entityA of type EntityA and
  • the EntityA has a property $entityBs, that contains an ArrayCollection of EntityB elements.

Now I want to remove some EntityB elements. It would work like this:

$entityManager->remove($myEntityB);
$entityManager->flush();

But I'd like to be able just to "say" $myEntityA->removeEntityB($entityB) and not need to care about anything else. An advantage would be, that I can implement a method EntityA#replaceEntityBs(ArrayCollection $entityBs), that simply removes all EntityA#$entityBs and replace them be the given elements.

Is it possible / How to remove elements of a collection directly from the inverse side of a relationship (of course without to pass the EntityManager into the entity)?

4

1 回答 1

0

The solution is to remove the reference to EntityA from the EntityB (first). In this case Doctrine will try to persist an EntityB without. But if we combine this with orphanRemoval=true, we'll get the aimed result:

class EntityA
{
    ...
    /**
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="EntityB", mappedBy="entityA", cascade={"persist"}, orphanRemoval=true)
     */
    protected $entityBs;
    ...
    public function removeEntityB(EntityB $entityB)
    {
        $this->entityBs->removeElement($entityB);
        $entityB->setEntityA(null);
        return $this;
    }
    ...
}

class EntityB
{
    ...
    /**
     * @var EntityA
     *
     * @ORM\ManyToOne(targetEntity="EntityA", inversedBy="entityBs")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="entity_a_id", referencedColumnName="id")
     * })
     */
    protected $entityA;
    ...
    /**
     * @param EntityA $entityA
     * @return EntityB
     */
    public function setEntityA(EntityA $entityA = null)
    {
        $this->entityA = $entityA;
        return $this;
    }
    ...
}

Off topic: Replacing a collection

Since I noted in the question, that an advantage would be, that one can implement a method like EntityA#replaceEntityBs(ArrayCollection $entityBs), I want to share here a possible implementation.

The first naïve attempt was just to remove all EntityBs and then add (and persist) the new elements.

public function setEntityBs($entityBs)
{
    $this->removeEntityBs();
    $this->entityBs = new ArrayCollection([]);
    /** @var EntityB $entityB */
    foreach ($entityBs as $entityB) {
        $this->addEntityB($entityB);
    }
    return $this;
}
public function removeEntityBs()
{
    foreach ($this->getEntityBs() as $entityB) {
        $this->removeEntityB($entityB);
    }
    return $this;
}

But if the input collection of the setEntityBs(...) contained existing EntityBs (that shold be updated), it led to deleting of them and only the new elements got persisted.

Here is a solution, that works as wished:

public function setEntityBs($entityBs)
{
    $this->removeEntityBsNotInList($entityBs);
    $this->entityBs = new ArrayCollection([]);
    /** @var EntityB $entityB */
    foreach ($entityBs as $entityB) {
        $this->addEntityB($entityB);
    }
    return $this;
}
private function removeEntityBsNotInList($entityBs)
{
    foreach ($this->getEntityBs() as $entityB) {
        if ($entityBs->indexOf($entityB) === false) {
            $this->removeEntityB($entityB);
        }
    }
}
于 2017-10-20T10:54:42.130 回答