3

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 entity Person, however this entity has no identity itself. You have to call EntityManager#persist() on the related entity and make sure that an identifier was generated before trying to persist Address. In case of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call EntityManager#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?

Alain Tiemblo
  • 36,099
  • 17
  • 121
  • 153
BenMorel
  • 34,448
  • 50
  • 182
  • 322

3 Answers3

3

How about use Identity through foreign Entities in doctrine 2.1?

In your scenario you can use @ManyToOne instead @OneToOne association.

Edit

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;
Community
  • 1
  • 1
manix
  • 14,537
  • 11
  • 70
  • 107
2

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 }
Community
  • 1
  • 1
Hajri Aymen
  • 620
  • 6
  • 18
  • On prePersist, the $entity has no id yet the first time. But the idea is nice, at least to order the code, will read the doc. – Alain Tiemblo Dec 25 '14 at 20:33
  • Then i think postPersist should do the job. You can get the entity Id and persist your entities. – Hajri Aymen Dec 25 '14 at 20:38
  • I shaked your solution with mine to finally end up with something that work very well with a simple `persist()`. Thanks for your time. – Alain Tiemblo Dec 26 '14 at 22:26
0

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 :-).

Alain Tiemblo
  • 36,099
  • 17
  • 121
  • 153