What makes something either a simple Model or an EAV Model in Magento is its Model Resource.
Whenever you’ve defined a custom model, resource model and collection, more than likely your resource model will extend the Mage_Core_Model_Resource_Db_Abstract
class.
An EAV resource model extends the Mage_Eav_Model_Entity_Abstract
class, rather than Mage_Core_Model_Resource_Db_Abstract
.
Their collections also extend the Mage_Eav_Model_Entity_Collection
class, rather than Mage_Core_Model_Resource_Db_Collection_Abstract
.
It’s worth noting that all models still extend from the Mage_Core_Model_Abstract
class.
Another difference between a non EAV model and an EAV model is the __construct
method within the resource model class.
Whenever you define a resource model for an EAV model.
public function __construct() { parent::__construct(); $this->setType(Mage_Catalog_Model_Product::ENTITY) ->setConnection('catalog_read', 'catalog_write'); $this->_productWebsiteTable = $this->getTable('catalog/product_website'); $this->_productCategoryTable = $this->getTable('catalog/category_product'); }
The key lines here are the setType()
and setConnection()
methods being used.
The setType()
method sets the entity type to catalog_product
as defined near the top of the Mage_Catalog_Model_Product
class.
const ENTITY = 'catalog_product';
A non EAV resource model uses the _construct()
method and not __construct()
.
protected function _construct() { $this->_init('newsletter/subscriber', 'subscriber_id'); $this->_subscriberLinkTable = $this->getTable('newsletter/queue_link'); $this->_read = $this->_getReadAdapter(); $this->_write = $this->_getWriteAdapter(); }
The key line here is the _init()
method which is used when you want to add our own custom resource model.
Now that we’ve seen some of the differences between the two models, we can take a look at how EAV models are loaded and saved.
If we look at the catalog product entity as an example, we use the following line to load a product with an ID of 1.
$product = Mage::getModel('catalog/product')->load(1);
So what happens next? The load()
method of the Mage_Core_Model_Abstract
class is called.
abstract class Mage_Core_Model_Abstract extends Varien_Object { .... public function load($id, $field=null) { $this->_beforeLoad($id, $field); $this->_getResource()->load($this, $id, $field); $this->_afterLoad(); $this->setOrigData(); $this->_hasDataChanges = false; return $this; } .... }
The _beforeLoad()
method simply adds the object, field and id into an array that can be used by looking onto a couple of events that are dispatched here: model_load_before
and core_abstract_load_before
.
protected function _beforeLoad($id, $field = null) { $params = array('object' => $this, 'field' => $field, 'value'=> $id); Mage::dispatchEvent('model_load_before', $params); $params = array_merge($params, $this->_getEventData()); Mage::dispatchEvent($this->_eventPrefix.'_load_before', $params); return $this; }
We then look for the model’s resource model by using the _getResource()->load()
method.
public function load($object, $entityId, $attributes = array()) { $this->_attributes = array(); return parent::load($object, $entityId, $attributes); }
This load()
method from the parent class is called. This class extends the Mage_Eav_Model_Entity_Abstract
class that contains the load()
method.
public function load($object, $entityId, $attributes = array()) { Varien_Profiler::start('__EAV_LOAD_MODEL__'); /** * Load object base row data */ $select = $this->_getLoadRowSelect($object, $entityId); $row = $this->_getReadAdapter()->fetchRow($select); if (is_array($row)) { $object->addData($row); } else { $object->isObjectNew(true); } if (empty($attributes)) { $this->loadAllAttributes($object); } else { foreach ($attributes as $attrCode) { $this->getAttribute($attrCode); } } $this->_loadModelAttributes($object); $object->setOrigData(); Varien_Profiler::start('__EAV_LOAD_MODEL_AFTER_LOAD__'); $this->_afterLoad($object); Varien_Profiler::stop('__EAV_LOAD_MODEL_AFTER_LOAD__'); Varien_Profiler::stop('__EAV_LOAD_MODEL__'); return $this; }
The interesting line to look at here is the $this->loadAllAttributes()
line.
public function loadAllAttributes($object=null) { $attributeCodes = Mage::getSingleton('eav/config') ->getEntityAttributeCodes($this->getEntityType(), $object); /** * Check and init default attributes */ $defaultAttributes = $this->getDefaultAttributes(); foreach ($defaultAttributes as $attributeCode) { $attributeIndex = array_search($attributeCode, $attributeCodes); if ($attributeIndex !== false) { $this->getAttribute($attributeCodes[$attributeIndex]); unset($attributeCodes[$attributeIndex]); } else { $this->addAttribute($this->_getDefaultAttribute($attributeCode)); } } foreach ($attributeCodes as $code) { $this->getAttribute($code); } return $this; }
A list of attribute codes are obtained and stored in the $attributeCodes
variable.
Back to the load()
method, the _loadModelAttributes()
method returns us a row of data within the catalog_product_entity
table.
protected function _loadModelAttributes($object) { if (!$object->getId()) { return $this; } Varien_Profiler::start('__EAV_LOAD_MODEL_ATTRIBUTES__'); $selects = array(); foreach (array_keys($this->getAttributesByTable()) as $table) { $attribute = current($this->_attributesByTable[$table]); $eavType = $attribute->getBackendType(); $select = $this->_getLoadAttributesSelect($object, $table); $selects[$eavType][] = $this->_addLoadAttributesSelectFields($select, $table, $eavType); } $selectGroups = Mage::getResourceHelper('eav')->getLoadAttributesSelectGroups($selects); foreach ($selectGroups as $selects) { if (!empty($selects)) { $select = $this->_prepareLoadSelect($selects); $values = $this->_getReadAdapter()->fetchAll($select); foreach ($values as $valueRow) { $this->_setAttributeValue($object, $valueRow); } } } Varien_Profiler::stop('__EAV_LOAD_MODEL_ATTRIBUTES__'); return $this; }
Returning back to the load()
method of the Mage_Core_Model_Abstract
class, the _afterLoad()
simply dispatches another two events.
protected function _afterLoad() { Mage::dispatchEvent('model_load_after', array('object'=>$this)); Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData()); return $this; }
Note that the load()
method is different in non EAV models whose resource models extends the Mage_Core_Model_Resource_Db_Abstract
class. We simply fetch the row from the database table.
public function load(Mage_Core_Model_Abstract $object, $value, $field = null) { if (is_null($field)) { $field = $this->getIdFieldName(); } $read = $this->_getReadAdapter(); if ($read && !is_null($value)) { $select = $this->_getLoadSelect($field, $value, $object); $data = $read->fetchRow($select); if ($data) { $object->setData($data); } } $this->unserializeFields($object); $this->_afterLoad($object); return $this; }
Moving onto saving data, let’s set our product’s name to something different for example and call the save()
method like the example below.
$product = Mage::getModel('catalog/product')->load(1); $product->setName("Some Product Name"); $product->save();
The save()
method of the Mage_Core_Model_Abstract
class is called.
public function save() { /** * Direct deleted items to delete method */ if ($this->isDeleted()) { return $this->delete(); } if (!$this->_hasModelChanged()) { return $this; } $this->_getResource()->beginTransaction(); $dataCommited = false; try { $this->_beforeSave(); if ($this->_dataSaveAllowed) { $this->_getResource()->save($this); $this->_afterSave(); } $this->_getResource()->addCommitCallback(array($this, 'afterCommitCallback')) ->commit(); $this->_hasDataChanges = false; $dataCommited = true; } catch (Exception $e) { $this->_getResource()->rollBack(); $this->_hasDataChanges = true; throw $e; } if ($dataCommited) { $this->_afterSaveCommit(); } return $this; }
Before we save the data, a couple of if statements are present to check whether the product should be deleted and whether or not the product model has actually changed.
Similar to the load()
process, we encounter _beforeSave()
and _afterSave()
methods. The _beforeSave()
looks like the following.
protected function _beforeSave() { if (!$this->getId()) { $this->isObjectNew(true); } Mage::dispatchEvent('model_save_before', array('object'=>$this)); Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData()); return $this; }
A check to see if the ID exists for our product is made. If it doesn’t, we simply mark the object as new. We also have the model_save_before
and core_abstract_save_before
events dispatched.
The line:
$this->_getResource()->save($this);
Will firstly fetch the model’s resource model and then call the save()
method. As we’re continuing with our product example, the resource model will be the same as when we were looking at the load()
example; Mage_Catalog_Model_Resource_Product
.
The Mage_Catalog_Model_Resource_Product
extends the Mage_Eav_Model_Entity_Abstract
class which contains the save()
method.
public function save(Varien_Object $object) { if ($object->isDeleted()) { return $this->delete($object); } if (!$this->isPartialSave()) { $this->loadAllAttributes($object); } if (!$object->getEntityTypeId()) { $object->setEntityTypeId($this->getTypeId()); } $object->setParentId((int) $object->getParentId()); $this->_beforeSave($object); $this->_processSaveData($this->_collectSaveData($object)); $this->_afterSave($object); .... }
The loadAllAttributes()
method will load all of the attributes from the EAV tables of the catalog_product
entity.
We then collect and process the save data that sit in between _beforeSave()
and _afterSave()
method of the EAV abstract class.
Returning back to Mage_Core_Model_Abstract, the _afterSave() method is run:
protected function _afterSave() { $this->cleanModelCache(); Mage::dispatchEvent('model_save_after', array('object'=>$this)); Mage::dispatchEvent($this->_eventPrefix.'_save_after', $this->_getEventData()); return $this; }
A summary of the load and save process is as follows.
load()
method of Mage_Core_Model_Abstract
is called._beforeLoad()
method of Mage_Core_Model_Abstract
is called that dispatches model_load_before
and core_abstract_load_before
events.load()
method of the model’s resource model is called which delegates to the load()
method of the Mage_Eav_Model_Entity_Abstract
class._afterLoad()
is called which dispatches model_load_after
and core_abstract_load_after
.setOrigData()
is then called.save()
method of Mage_Core_Model_Abstract
is called._beforeSave()
method of Mage_Core_Model_Abstract
is called that dispatches model_save_before
and core_abstract_save_before
events.save()
method of Mage_Eav_Model_Entity_Abstract
is called._beforeSave()
of the model’s resource model is called.processData()
is then called._afterSave()
of the model’s resource model is called._afterSave()
method of Mage_Core_Model_Abstract
is called that dispatches model_save_after
and core_abstract_save_after
events.afterCommitCallback()
is then called.Note: This article is based on Magento Community/Open Source version 1.9.