Monday, February 1, 2010

Save an object into the database with a specific id. (insert - not update)

Problem can be found here:

Possible solution:

The presence of an id means update and if the id is null it means is insert.
So when you try to insert and add an id Magento is actually trying to update the entity but the id you specify does not exist.
It's something similar to this:
UPDATE some_table SET some_field = some_value WHERE id = some_id; 
The sql goes through but nothing gets updated.
If you want to modify something (but I don't advice you to do this) you can change the Mage_Core_Model_Mysql4_Abstract and Mage_Core_Model_Abstract models (the save() method for both of them.)

If you look inside the save method of Mage_Core_Model_Mysql4_Abstract you will see that there's an 'if' statement
if (!is_null($object->getId())) {...}
This means exactly what I explained above.

Here is an idea on how you can make magento do an insert with a specified id.
Overwrite the Mage_Core_Model_Abstract::save() method to accept and other parameter.
Let's call it $alwaysInsert and set it's default value to false (so you will not interfere with the other objects that use save()).
Then pass the $alwaysInsert parameter to the save method from the resource.
Your new method could look like this:
public function save($alwaysInsert = false)
{
$this->_getResource()->beginTransaction();
try {
$this->_beforeSave();
$this->_getResource()->save($this, $alwaysInsert);
$this->_afterSave();

$this->_getResource()->commit();
}
catch (Exception $e){
$this->_getResource()->rollBack();
throw $e;
}
return $this;
}

In Mage_Core_Model_Mysql4_Abstract, change the method save() to accept 2 parameters.
It could look like this:
public function save(Mage_Core_Model_Abstract $object, bool $alwaysInsert)
{
if ($object->isDeleted()) {
return $this->delete($object);
}

$this->_beforeSave($object);
$this->_checkUnique($object);

if (!is_null($object->getId()) && !$alwaysInsert) {
$condition = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId());
$this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition);
} else {
$this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object));
$object->setId($this->_getWriteAdapter()->lastInsertId($this->getMainTable()));
}

$this->_afterSave($object);

return $this;
} 

WARNING:
There are 2 issues (at least that is all I can think about now)
1. I didn't test this. You can test it and tell everyone the results
2. I'm not sure how Magento behaves when extending an abstract class. Normally it shouldn't cause any problems, but make sure the class(es) you are working with extend the new class(es) that you just created (extensions of Mage_Core_Model_Abstract and Mage_Core_Model_Mysql4_Abstract not these classes themselves).

If instead of overwriting you decide to modify the core this should take care of these issues, but you are not suppose to modify the code.
I hope this works for you. If it doesn't I'm sorry.

1 comment:

  1. Ahh this is an issue I was having recently while trying to back-up some models and restore them to the database from PHP.

    Magento relies on the auto-increment value a lot and they assume that you will rarely ever assign the primary key value (main index) when making a save.

    I'm looking forward to implementing your ideas since I temporarily abandoned the issue and I appreciate you making this post!

    ReplyDelete