Skip to content

Persisting entities with OneToOne and OneToMany/ManyToOne relationship between them. #6499

@Frikkle

Description

@Frikkle

Last week I encountered an issue in one of our projects where we had two entities, call them A and B having a unidirectional OneToOne A->B (with the join column not nullable) and a OneToMany(A->B)/ManyToOne(B->A) bidirectional relationship between them.

The problem lies in the order of persisting them; when first persisting A, then B, a constraint violation is thrown: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: a.b_id, indicating that the join column isn't allowed to be NULL, even though it should have been set.

I reworked my code to a minimal example:

Entity A

/**
 * Class A
 * @ORM\Entity()
 */
class A
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(name="id", type="integer")
     */
    private $id;

    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\B", mappedBy="a", cascade={"persist", "remove"}, orphanRemoval=true)
     */
    private $bs;

    /**
     * @ORM\OneToOne(targetEntity="AppBundle\Entity\B", cascade={"persist"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $b;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return B[]|ArrayCollection
     */
    public function getBs()
    {
        return $this->bs;
    }

    /**
     * @param B $b
     */
    public function addB(B $b)
    {
        if ($this->bs->contains($b)) return;

        $this->bs->add($b);

        // Update owning side
        $b->setA($this);
    }

    /**
     * @param B $b
     */
    public function removeB(B $b)
    {
        if (!$this->bs->contains($b)) return;

        $this->bs->removeElement($b);

        // Not updating owning side due to orphan removal
    }

    /**
     * @return B
     */
    public function getB()
    {
        return $this->b;
    }

    /**
     * @param B $b
     */
    public function setB(B $b)
    {
        $this->b = $b;
    }
}

Entity B

/**
 * Class B
 * @ORM\Entity()
 */
class B
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(name="id", type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\A", inversedBy="bs", cascade={"persist"})
     */
    private $a;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return A
     */
    public function getA()
    {
        return $this->a;
    }

    /**
     * @param A $a
     */
    public function setA(A $a)
    {
        $this->a = $a;
    }
}

My persist code

$a = new A();
$entityManager->persist($a);

$b = new B();
$a->setB($b);
$entityManager->persist($b);

$entityManager->flush();

The code above throws the constraint violation error. At first I thought it might be related to circular cascade persists. But if I remove all cascade annotation and the orphanRemoval clauses as well, it still throws the ConstraintViolationException.

What does work?
It does however seem to work when I revert the persist order, like this:

$a = new A();
$b = new B();
$a->setB($b);

$entityManager->persist($b);
$entityManager->persist($a);

$entityManager->flush();

And, it also works when wrapping the whole operation in a transaction. But somehow I have the feeling that it should work.

Possibly related issues
When looking through the issue list I came across #4090 which seems to be a similar issue. Maybe this helps to shine light on that as well?

Our environment
We're working in Symfony 2.8 LTS using the doctrine/doctrine-bundle version 1.6.4 and doctrine/orm version 2.5.5. After upgradring both of them to the latest version, the problem persisted.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions