Details
Description
I try to explain through an example.
Data model:
===========
User — UserGroup — Group // many-to-many relation
Example code:
=============
$user = new User; $user->name = 'user1'; $user->Group[0]->name = 'group1'; // id => 1 $user->Group[1]->name = 'group2'; // id => 2 $user->save(); $group3 = new Group; $group3->name = 'group3'; $group3->save(); // id => 3 // edit user in an HTML form, and select all groups with checkboxes // $_POST = array('name' => 'user1', 'Group' => array(1, 2, 3)) $user->fromArray($_POST); $user->save(); // fail, unique constraint violation
Reason:
=======
Doctrine_Record::fromArray() calls unlink() in line 1948: $this->unlink($key, array(), false); unlink() fills _pendingUnlinks variable with User's Group relation IDs in line 2418: if ( ! $ids) { $ids = $allIds; } foreach ($ids as $id) { $this->_pendingUnlinks[$alias][$id] = true; }
So _pendingUnlinks[$alias] will be an array.
Then fromArray() calls link() in line 1949:
foreach ($value as $id) {
$this->link($key, $id, false);
}
link() remove new IDs (1, 2 and 3) from _pendingUnlinks[$alias] in line 2496:
foreach ($ids as $id) {
if (isset($this->_pendingUnlinks[$alias][$id])) {
unset($this->_pendingUnlinks[$alias][$id]);
}
}
So _pendingUnlinks[$alias] will be an empty array.
When we save user, this code is executed in Doctrine_Connection_UnitOfWork::saveGraph() (in line 103):
foreach ($record->getPendingUnlinks() as $alias => $ids) {
if ($ids === false) {
$record->unlinkInDb($alias, array());
} else if ($ids) {
$record->unlinkInDb($alias, array_keys($ids));
}
}
BUT
Because _pendingUnlinks[$alias] is an empty array, neither IF branch will be executed, so relations won't be deleted.
And then Doctrine try to insert new relations, but it will fail because it will violate existed primary keys in UserGroup.
In an earlier version of Doctrine-1.2, the unlink() method set _pendingUnlinks[$alias] to false if $ids attribute was an empty array, so in saveGraph() all relations were deleted before new relations inserted.
/Sorry for my poor English/