In latest Doctrine on Symfony2 trying to work out multiple bidirectional relationship between two objects.
Person owner object has one postal Address and then multiple secondary Addresses in a collection, and I remove() the Person, I want all of it's addresses to also be deleted (but removing an address should not remove a Person), but I'm getting this error -
An exception occurred while executing 'DELETE FROM address WHERE id = ?' with
params {"1":"fb5e47de-2651-4069-b85e-8dbcbe8a6c4a"}:
[PDOException] SQLSTATE[23000]: Integrity constraint violation: 1451
Cannot delete or update a parent row: a foreign key constraint fails
(`db`.`address`, CONSTRAINT `FK_633704 C29C1004E`
FOREIGN KEY (`person_id`) REFERENCES `person` (`id`))
in
class Person
{
/**
* @var Address postalAddress
*
* @ORM\OneToOne(targetEntity="Address", cascade={"all"}, orphanRemoval=true)
* @ORM\JoinColumn(onDelete="cascade")
*/
private $postalAddress;
/**
* @var \Doctrine\Common\Collections\Collection otherAddresses
*
* @ORM\OneToMany(targetEntity="Address", mappedBy="person", cascade={"all"}, orphanRemoval=true)
*/
private $otherAddresses;
}
class Address
{
/**
* @var Person person
*
* @ORM\ManyToOne(targetEntity="Person", inversedBy="postalAddress, otherAddresses")
* @ORM\JoinColumn(nullable=false)
*/
private $person;
}
I thought it might because the
inversedBy="postalAddress, otherAddresses"
I don't think multiple inversedBy is supported; then I also tried to change
@ORM\JoinColumn(nullable=false)
to be nullable, but I still get the error.
This is obviously not about trivial Person/Address example but something more complex, but this was my best attempt at abstraction.
I'm sure I've missed something obvious. Can anyone help?
While what you are doing may make sense from a purely logical standpoint, it does not from a relational-data standpoint, and particularly makes no sense from Doctrine's perspective.
Doctrine is trying to maintain 3 different relationships:
--Many:One-->
$otherAddresses Person
--Many:One-->
$postalAddress Person
--One:One-->
$id Address
You see the problem?
The simple solution here is to use the VERY common design pattern of setting a primary for a collection. In essence, you only need one relationship:
--Many:One-->
$otherAddresses Person
Then, add to address a property which defines that address to be the primary. Programattically handle this in the Person and Address entities:
Class Person
{
. . .
public function getPrimaryAddress() {
if (null === $this->primaryAddress) {
foreach($this->getOtherAddresses() as $address) {
if ($address->isPrimary()) {
$this->primaryAddress = $address;
break;
}
}
}
return $this->primaryAddress;
}
// similar for the setter, update the old address as not primary if there is one, set the new one as primary.
}
If you keep the One-to-One relationship UNIDIRECTIONAL from Person to Address, the problem solves itself.
--Many:One-->
$otherAddresses Person
--One:One-->
Address
You're still going to have trouble here though, because Doctrine will complain if:
- the main (PostalAddress) address doesn't have both sides for the Many:One defined. (so your "main" address will also have to be in the $otherAddresses
collection).
- attempting to remove or cascade deletes and updates will result in those two relationships conflicting, "crossing streams" of doctrine's relational constraints, so you'll have to programatically handle those operations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With