I’ve got a relationship between three entities that seems to be causing some remove-before-updating problems.
Setup
Here are pared-down versions of those three entities:
Step
/**
* @ORM\Entity
* @ORM\Table(name="steps");
* @ORM\Entity(repositoryClass="Lemonade\Repository\StepRepository");
*/
class Step implements TimestampedEntityInterface, AdminDisplayInterface, LocalizedObjectInterface, AlpineModelProviderInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer", nullable=false, options={"unsigned"=true})
*
* @var int
*/
private $id;
/**
* @ORM\OneToMany(targetEntity="Lemonade\Entity\StepSkillReward", mappedBy="step", cascade={"all"}, orphanRemoval=true)
*
* @var Collection
*/
private $skill_rewards;
}
StepSkillReward
/**
* @ORM\Entity
* @ORM\Table(name="steps_skills_rewards")
*/
class StepSkillReward
{
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Lemonade\Entity\Step", inversedBy="skill_rewards")
* @ORM\JoinColumn(name="step_id", referencedColumnName="id", onDelete="cascade")
*
* @var Step
*/
private $step;
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Lemonade\Entity\Skill")
* @ORM\JoinColumn(name="skill_id", referencedColumnName="id", onDelete="cascade")
*
* @var Skill
*/
private $skill;
/**
* @ORM\Column(type="integer", nullable=false, options={"default":0, "unsigned":true})
*
* @var int
*/
private $points_rewarded;
}
Skill
/**
* @ORM\Entity
* @ORM\Table(name="skills")})
*/
class Skill implements AlpineModelProviderInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer", nullable=false, options={"unsigned"=true})
*
* @var int
*/
private $id;
}
Form/Hydration Issue
The problem I’m running into only occurs when you are adding and removing a sub-entity at the same time in a single update. A simple removal or a simple addition works as expected.
Focusing on the problem, when attempting to add and remove at the same time, it tries to update the old entity which causes a duplicate key problem. Let me better explain:
Imagine Step with id 800, has these StepSkillReward entities:
step, skill, points_rewarded
800, 1, 5
800, 2, 10
Then, when saving/hydrating the form, we are removing the relationship with skill 2, and are adding a new one with skill three (with points_rewarded of 5)
step, skill, points_rewarded
800, 1, 5
800, 3, 5
The expected behavior would be that Doctrine remove entity 800,2 before adding 800,3. Instead, the behavior I’m getting is a duplicate key SQL explosion like this:
UPDATE steps_skills_rewards
SET points_rewarded = ?, skill_id = ?
WHERE step_id = ? AND skill_id = ?
WITH PARAMS [5, 3, 800, 1]:
Paraphrasing, instead of doing:
DELETE {800, 2, 10}
INSERT {800, 3, 5}
It’s trying to do
UPDATE {800,3,5} TO {800,1,5}
INSERT {800,3,5}
But at the first operation, that entry already exists in the database.
Sorry for the eyeball scratcher - anyone seen this?
Thanks!