Shroder
Shroder

Reputation: 593

Inverse association assignment in Doctrine2

Does Doctrine2 support the ability to update an association when it is changed from the inverse side? And if not out of the box, is there a plugin/extension I can use to cover this behavior?

Here is an example of what I have:

Organization
/**
 * @Id
 * @Column(type="integer")
 */
    protected $id

/**
 * @ManyToOne(targetEntity="Organization", inversedBy="children", cascade={"persist"})
 * @JoinColumn(name="parent_org_id", referencedColumnName="id")
 */
protected $parent;

/**
* @OneToMany(targetEntity="Organization", mappedBy="parent", cascade={"persist"})
*/
protected $children;    

Upvotes: 0

Views: 99

Answers (1)

Ezequiel Muns
Ezequiel Muns

Reputation: 7742

No. Doctrine 2 tracks associations through the owning side and as it tries to have minimal effect on the behavior of your entities, it wouldn't want to add this kind of functionality.

The standard way to track changes on the inverse side is to ensure that they remain in sync by adding logic to your entities that updates the owning side when making changes on the inverse.

In your example, you could have addChild, removeChild and setParent functions that do something like this:

public function addChild(Organization $child) 
{
    $this->children[] = $child;
    $child->setParent($this); // < update the owning side
}

public function removeChild(Organization $child) 
{
    $this->children->removeElement($child);
    $child->setParent(null); // < update the owning side
}

public function setParent(Organization $parent = null) 
{
    $this->parent = $parent;
}

You can see that now there is a new problem, you must always use the addChild/removeChild functions (i.e. make changes on the inverse side) for the owning side remain in sync (or sync it yourself, as the caller). This leads to you having to create a policy, either callers must always update on the owning side, or the inverse side.

You could make the setParent function update the inverse side also, but you must be very careful as that can easily lead to infinite recursion:

public function addChild(Organization $child) 
{
    $this->children[] = $child;
    // safely update the owning side
    if ($child->getParent() != $this) {
       $child->setParent($this);
    }
}

public function removeChild(Organization $child) 
{
    $this->children->removeElement($child);
    // safely update the owning side
    if ($child->getParent() == $this) {
        $child->setParent(null); 
    }
}

public function setParent(Organization $parent = null) 
{
    $oldParent = $this->parent;
    $this->parent = $parent;
    // update the inverse side
    if ($oldParent) {
        $oldParent->removeChild($this); // will not call setParent
    }
    if ($this->parent) {
        $this->parent->addChild($this); // will not call setParent
    }
}

Apart from the added complexity, this scheme is not ideal when for example moving lots of children from one parent to another, because removeChild takes linear time, creating a O(n^2) running time for the move.

Upvotes: 1

Related Questions