简体   繁体   中英

Symfony SQL error when saving related Doctrine entities using embeded collection of forms

I have been following the How to Embed a Collection of Forms example on the Symfony website.

My situation is a little different. I have 2 Doctrine entities Experiment and Goal with a composite primary key relationship (see code below).

Basically one Experiment can have many Goals , but the goal ID is only unique in combination with the Experiment ID and User ID.

class Goal
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", options={"default": 1})
     * @ORM\Id
     */
    protected $id = 1;

    /**
     * @var integer
     *
     * @ORM\Column(name="experiment_id", type="integer", nullable=true, options={"default": null})
     * @ORM\Id
     */
    protected $experimentId;

    /**
     * @ORM\ManyToOne(targetEntity="Experiment", inversedBy="goals")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="experiment_id", referencedColumnName="id"),
     *   @ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
     * })
     */
    protected $experiment;

    /**
     * @var integer
     *
     * @ORM\Column(name="user_id", type="integer", nullable=true, options={"default": null})
     * @ORM\Id
     */
    protected $userId;

    /**
     * @ORM\ManyToOne(targetEntity="Optimcore\UserBundle\Entity\User", inversedBy="goals", cascade={"persist"})
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     */
    protected $user;

    // ...
}

I have an ExperimentType form that embeds a 'collection' of GoalType forms.

in my controller I have the following code:

$originalGoals = new ArrayCollection();
foreach ($experiment->getGoals() as $goal) {
    $originalGoals->add($goal);
}

$form = $this->createForm(new ExperimentType(), $experiment, array(
    'action' => $this->generateUrl('experiment_goals', array('id' => $experiment->getId())),
    'method' => 'PUT',
));
$form->handleRequest($request);

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

    // remove the relationship between the Goal and the Experiment
    foreach ($originalGoals as $goal) {
        if (false === $experiment->getGoals()->contains($goal)) {
            $goal->setExperiment(null);
            $em->persist($goal);
            $em->remove($goal);
        }
    }

    foreach ($experiment->getGoals() as $goal) {
        if (is_null($goal->getUserId())) {
            $this->getUser()->addGoal($goal);
            $goal->setUserId($this->getUser()->getId());
            $goal->setUser($this->getUser());
            $em->persist($goal);
        }
    }

    $em->flush();

}

I have javascript to add and remove goal forms on the page. New goals are assigned the next available ID, so if the last goal on the page has the ID 2 the next goal is given the ID 3.

Adding new goals and saving to the DB works fine, deleting goals and saving also works fine.

However when a goal is deleted, by the javascript on the page, and then a new goal is added by javascript this goal is assigned the same ID as the goal that was deleted. Now when the form is submitted and Doctrine tries to save the Goals an SQL error is thrown because Doctrine tries to INSERT a Goal with the same ID as the one deleted by the Javascript but still in the database.

How do I go about getting Doctrine to delete the first Goal before inserting the new one? I've tried flushing earlier but this doesn't help. How do I force Doctrine to only delete the old Goal before adding saving the new one? or am I going about this all wrong?

Here's the error message:

An exception occurred while executing 'INSERT INTO goal (id, experiment_id, user_id, name) VALUES (?, ?, ?, ?)' with params ["2", 183, 6, "Goal 2"]:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2-183-6' for key 'PRIMARY'

A very simple answer to a very long question was found in the Doctrine documentation .

10.2. How Doctrine Detects Changes

  • Flush only a single entity with $entityManager->flush($entity) .

To get Doctrine to delete the original Goal before saving the new one I have to add $em->flush($goal); after $em->remove($goal); . This will flush only that single entity, deleting the record from the database.

foreach ($originalGoals as $goal) {
    if (false === $experiment->getGoals()->contains($goal)) {
        $goal->setExperiment(null);
        $em->persist($goal);
        $em->remove($goal);

        // commit this change to the database
        $em->flush($goal);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM