Brian
Brian

Reputation: 7135

How to persist entities from an embedded form submission?

I have a Person entity and an Address entity, set up with a bi-directional one-to-one relationship, with the FK on Address:

...

class Person
{
    ...
    /**
     * @ORM\OneToOne(targetEntity="Address", mappedBy="person")
     */
    protected $address;
    ...
}

...

class Address
{
    ...
    /**
     * @ORM\Id
     * @ORM\OneToOne(targetEntity="Person", inversedBy="address")
     * @ORM\JoinColumn(name="personID", referencedColumnName="id")
     */
    protected $person;
}

The Address Entity does NOT have a dedicated primary key, it instead derives its identity through the foreign key relationship with Person, as explained here

The form I have for creating a new Person also embeds the form for Address. When the form is submitted, this controller action is executed:

public function createAction(Request $request)
{
    $person = new Person();

    $form = $this->createCreateForm($person);
    $form->handleRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($person);
        $em->flush();

        return $this->redirect($this->generateUrl('people'));
    }

    return array(
        'person' => $person,
        'form' => $form->createView(),
    );
}

I've inspected the data, and $person has its $address property filled out with the proper form details as expected. However, once the $person object is persisted, I get the error:

A new entity was found through the relationship 'Acme\AcmeBundle\Entity\Person#address' that was not configured to cascade persist operations for entity...

I've tried a couple of things and none seem to work:

  1. Setting cascade={"persist"} on the OneToOne relationship definition on the Person object. Doing so results in error:

    Entity of type Acme\AcmeBundle\Entity\Address is missing an assigned ID for field 'person'...

  2. On the Person#setAddress method, I've taken the $address parameter and manually called $address->setPerson($this) on it. Doesn't work either.
It seems like my problem is that Doctrine is trying to save the Address object before saving the Person object, and it can't because it needs to know the ID of the associated Person first.

For instance, If I alter the the persist code to something like this, it works:

...
// Pull out the address data and remove it from the Person object
$address = $person->getAddress();
$person->setAddress(null);

// Save the person object and flush so we get an ID
$em->persist($person);
$em->flush();

// Now set the person object on the address and save the address
$address->setPerson($person);
$em->persist($address);
$em->flush();
...

How can I do this properly? I want to retain the ability to embed forms with this type of one-to-one relationship, but things are starting to get complicated. How do I get Doctrine to flush the $person object before flushing the $address object, without manually doing it myself like above?

Upvotes: 3

Views: 2123

Answers (2)

jsampedro77
jsampedro77

Reputation: 86

Your mapping is incorrect. You are using @ORM\Id incorrectly on $person.

If you haven't yet, add a real $id to Address and add again `cascade={"persist"}.

class Person
{
    ...
    /**
     * @ORM\OneToOne(targetEntity="Address", mappedBy="person", cascade={"persist"})
     */
    protected $address;
    ...
}

...

class Address
{
    ...
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\OneToOne(targetEntity="Person", inversedBy="address")
     * @ORM\JoinColumn(name="personID", referencedColumnName="id")
     */
    protected $person;
}

If Person were the owning side, Address should also be automatically persisted by Doctrine, don't know your model but maybe you should consider changing it.

Upvotes: 1

Cerad
Cerad

Reputation: 48865

Keep the cascade=persist.

Then modify Person::setAddress

class Person
{
    public function setAddress($address)
    {
        $this->address = $address;
        $address->setPerson($this); //*** This is what you are missing ***

This is a very common question but it's hard to search for.

Upvotes: 4

Related Questions