Reputation: 36514
I want two tables, Person
and Address
, to share their primary key:
CREATE TABLE Person (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE Address (personId INT UNSIGNED NOT NULL PRIMARY KEY);
I'm trying to model the idea that when you create a Person
, you're also creating an Address
for it.
I came up with the following mapping:
class Person
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
/**
* @OneToOne(targetEntity="Address", cascade={"all"})
* @JoinColumn(name="id", referencedColumnName="personId")
*/
private $address;
public function __construct() {
$this->address = new Address($this);
}
}
class Address
{
/**
* @Id
* @OneToOne(targetEntity="Person")
* @JoinColumn(name="personId", referencedColumnName="id")
*/
private $person;
public function __construct(Person $person) {
$this->person = $person;
}
}
When I try to persist Person
, I would expect it to cascade to Address
, but I get the following exception:
Doctrine\ORM\ORMException
Entity of type
Address
has identity through a foreign entityPerson
, however this entity has no identity itself. You have to callEntityManager#persist()
on the related entity and make sure that an identifier was generated before trying to persistAddress
. In case of Post Insert ID Generation (such as MySQLAuto-Increment
or PostgreSQLSERIAL
) this means you have to callEntityManager#flush()
between both persist operations.
I can't explicitly call flush()
, because I'm not explicitly persist()
ing Address
, instead I'm expecting Person
to cascade persist Address
.
So as far as I understand it, it should be Doctrine's job to flush()
at the right moment to get the id
.
This one-to-one relationship cascades fine when using different auto_increment
ids for both Person
and Address
(but then I have to add an addressId
to Person
).
My aim is to make them share the same id
.
Is that possible?
Upvotes: 4
Views: 4412
Reputation: 630
You can use the symfony listener and remove the auto increment strategy from adress entity :
First create a Listner and crete your listner class like AdressGenerator.php
<?php
namespace Zodiac\HelperBundle\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use AAA\XXXBundle\Entity\Person;
use AAA\XXXBundle\Entity\Adress;
class AdressGenerator
{
public function prePersist(LifecycleEventArgs $args)
{
// This function is called just beforethe event Persist
// The AdressGeneratorlistner is defined in the config.yml file
// Get the entity manager and the entity from le LifecycleEventArgs
$em = $args->getEntityManager();
$entity = $args->getEntity();
if ($entity instanceof Person) {
//Create the Adress entity
$adress = new Adress();
$adress->setId($entity->getId());
$em->persist($adress);
$entity->setAdress($adress);
$em->persist($entity);
$em->flush();
}
}
}
Then add your listener service in the config.yml:
services:
AdressGenerator.listener:
class: AAA\xxxBundle\Listener\AdressGenerator
tags:
- { name: doctrine.event_listener, event: prePersist }
Upvotes: 2
Reputation: 36954
I found a solution but I don't know if that's the best (it looks heavy to me).
That's not a mapping problem, but the way we persist :
Instead of doing:
$em->persist($person);
$em->flush();
We should do:
$em->transactional(function($em) use ($person)
{
$address = $person->getAddress();
$person->setAddress(null);
$em->persist($person);
$em->flush();
$address->setPerson($person);
$em->persist($address);
$em->flush();
});
If somebody has a cleaner solution, (s)he'll get the bounty :-).
Upvotes: 0
Reputation: 14747
How about use Identity through foreign Entities in doctrine 2.1?
In your scenario you can use @ManyToOne
instead @OneToOne
association.
The association is persisted when you persist the owning side. In your mapping, that is the Address
. Change your mapping so the owner has the foreign key and everything should work fine.
/**
* @OneToOne(targetEntity="Address", cascade={"all"}, inversedBy="person")
* @JoinColumn(name="id", referencedColumnName="personId")
*/
private $address;
In other words, if Address
should be owner by the Person
then you must persist Address
at first place to in order to persist the association.
If you want the association to be persisted with Person
, then you should mark the Person
to be the owning side and Address
to be the inverse side:
/**
* @Id
* @OneToOne(targetEntity="Person", inversedBy="address")
* @JoinColumn(name="personId", referencedColumnName="id")
*/
private $person;
Upvotes: 3