-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
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.