Magento EAV Concepts

In the previous blog post we saw what is the use of magento eav tables and understood the db structure of tables.
In this blog post we will see the important classes and other implementation aspects of magento eav model.

EAV Classes

When defining an EAV entity we first need to create eav model class. The default base module class which magento provides is “Mage_Eav_Model_Entity” which extends “Mage_Eav_Model_Entity_Abstract”.
The abstract class is the main class which contains all required functions to EAV entity save, delete, load, update etc. The abstract class extends “Mage_Core_Model_Resource_Abstract”

Once the entity module class is defined, we need to define a attribute model class as well. We know that in EAV module each entity needs to have multiple attributes, so we need to define a default attribute class.
Magento provides a base class for this “Mage_Eav_Model_Entity_Attribute” which extends “Mage_Eav_Model_Entity_Attribute_Abstract”

Each attribute in eav model has source model, backend moduel, frontend module.
Default backend model is : Mage_Eav_Model_Entity_Attribute_Backend_Default
Default source model is: Mage_Eav_Model_Entity_Attribute_Source_Config
Default frontend model is: Mage_Eav_Model_Entity_Attribute_Frontend_Default

Backend, Source, Frontend Model

Each attribute of an eav model has a backend, source, frontend model.

Backend Model is used for database operations relating to an attribute. An attribute can have a custom backend model defined, but if its defined as NULL or false then the default backend module is “Mage_Eav_Model_Entity_Attribute_Backend_Default” which extends “Mage_Eav_Model_Entity_Attribute_Backend_Abstract”
So if your creating your own custom backend module it should extend the Abstract class.
The abstract class as functions like “validate()”, “beforeSave()”, “afterSave()”, “afterLoad()”, “beforeDelete()”, “afterDelete()” which can be extended in your custom backend model.

Source Model is used to display pre-existing data for an attribute. Either through configuration files or through database table.
Base classes for source module are
1. Mage_Eav_Model_Entity_Attribute_Source_Config : this displays values configuration file. You need to set

$this->_configNodePath = 'global/catalog/product/design/options_container';

and it will read from _configNodePath

2. Mage_Eav_Model_Entity_Attribute_Source_Table: this display values from the table ‘eav_entity_attribute_option’
This is mostly used for dropdown attributes

3. Mage_Eav_Model_Entity_Attribute_Source_Store: this simply shows a list of stores as option array.

4. Mage_Eav_Model_Entity_Attribute_Source_Boolean: it has a Yes/No options array

The main class to extend in source model is “getAllOptions()” and this method should return an options array.

Frontend Model as the name suggest is used for display purposes. This mainly used to format/modify data during display. “Mage_Eav_Model_Entity_Attribute_Frontend_Default” is the default class. The main function to override in frontend model is “getValue()” which return the formatted value.

EAV Entity Save Process

Let’s look at how an eav entity gets saved to database and what are the steps which happen during this process.
Before looking into the code of how this works, first there a table which we need to look at.
The table name “eav_entity_type”, this table has the base definition of each entity magento.
This has columns like
entity_type_code : this is a unique code each entity and is used to identify a single entity. The default values already present in magento for existing entities are ‘customer’, ‘customer_address’, ‘catalog/category’ ,’catalog/product’, ‘order’, ‘invoice’, ‘creditmemo’, ‘shipment’
entity_model: this specifies the main entity model file for each entity e.g catalog/product, catalog/category these are resource models
attribute_model: this is the default attribute model for entity, it can be null as which in which it will take default attribute model as discussed above.
entity_table: table name where entity details need to be stored
value_table_prefix: this is the prefix to be used when storing data in entity value table. will see this in action later on. this can be null in which case prefix is taken as empty
entity_id_field: this is the name of primary key column name. this can be null and it defaults to “id”
is_data_sharing:
data_sharing_key:
default_attribute_set_id: as the name it has the id of default attribute set of the entity
increment_model: the model use to generated increment id, mainly used for orders, invoice, shipment etc where increment id is required. model can be numeric or alphanumeric
increment_per_store:
increment_pad_length:
increment_pad_char:
additional_attribute_table:
entity_attribute_collection: the collection which is used to retrieve all attributes of an entity. if null defaults to “Mage_Eav_Model_Resource_Entity_Attribute_Collection”

Now lets look at the save() process. The save() process is defined in Mage_Eav_Model_Entity_Abstract class
Below is the php code for save, lets look at the important parts in detail

    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);

        return $this;
    }

$this->loadAllAttributes()
This function load all attributes of a EAV model.
The first step in this getting all attributes for the entity model. The code for this is written in “eav/config” class in “getEntityAttributeCodes()” function. The code is

                 $attributesInfo = Mage::getResourceModel($entityType->getEntityAttributeCollection())
                    ->setEntityTypeFilter($entityType)
                    ->setAttributeSetFilter($attributeSetId)
                    ->addStoreLabel($storeId)
                    ->getData();

Here we use “entity attribute collection” and “attribute set id” which are both defined in “eav_entity_type” table as mentioned above.
What this does is joins data from 3 tables “eav_entity_attribute”, “eav_attribute” and additional table if any. Additional table is defined “eav_entity_type” table.

$this->_beforeSave()
This function has the code

$this->walkAttributes('backend/beforeSave', array($object));

So this basically iterates overall attributes and calls the beforeSave() on each attribute’s backend model

$this->_collectSaveData()
This function returns an array of type

 array (
       'newObject', 'entityRow', 'insert', 'update', 'delete'
  )

newObject => this basically has all attribute values in key value format ($this->_data)
entityRow => this is a key/value array of all static attribute values. Static attributes is the data which is directly present in main eav_entity table. e.g the entity table “catalog_product_entity” has columns type_id, sku,created_at, updated_at which are all considered static attribute since these don’t need to be saved in a seperate table.
insert => the contains array of attributes which needs to be inserted
update => an array of attributes which needs to be updated
delete => an array of attributes which needs to be deleted

$this->_processSaveData()
This process the above array and carries out actual database operation

$this->_afterSave()
This has the code

$this->walkAttributes('backend/afterSave', array($object));

So this function iterates over all backend models and calls afterSave() function

Interestingly enough there are no events dispatched during this process.

How Does Magento Decide To Save/Update An Entity
In the function “_processSaveData()” above there is a cod

       if (!empty($entityId) && is_numeric($entityId)) {
            $bind   = array('entity_id' => $entityId);
            $select = $adapter->select()
                ->from($entityTable, $entityIdField)
                ->where("{$entityIdField} = :entity_id");
            $result = $adapter->fetchOne($select, $bind);
            if ($result) {
                $insertEntity = false;
            }
        } else {
            $entityId = null;
        }

here magento loads data from entityTable based on entityIdField. If data is found it updates data else it inserts.

Entity Load Process

The load process is defined in “load()” function in Mage_Eav_Core_Entity_Abstract class.
The first important code in this function is

        $select  = $this->_getLoadRowSelect($object, $entityId);
        $row     = $this->_getReadAdapter()->fetchRow($select);

        if (is_array($row)) {
            $object->addData($row);
        } else {
            $object->isObjectNew(true);
        }

This loads the base row or the static attributes from entity table. Next we have

$this->loadAllAttributes($object);

We have already seen this above, it loads all attributes from tables. Next we have

$this->_loadModelAttributes($object);

This function basically creates a select query for each attribute type table. e.g for catalog/product entity type it generates attributes for

catalog_product_entity_int
catalog_product_entity_decimal
catalog_product_entity_varchar
catalog_product_entity_text
catalog_product_entity_datetime

and then sets the value in the main object and backend model

Next we have

$this->_afterLoad($object);

This iterates over all attributes and calls the afterLoad function on attribute backend models.

Entity Delete Process

Below the code for deleting an entity

    public function delete($object)
    {
        if (is_numeric($object)) {
            $id = (int)$object;
        } elseif ($object instanceof Varien_Object) {
            $id = (int)$object->getId();
        }

        $this->_beforeDelete($object);

        try {
            $where = array(
                $this->getEntityIdField() . '=?' => $id
            );
            $this->_getWriteAdapter()->delete($this->getEntityTable(), $where);
            $this->loadAllAttributes($object);
            foreach ($this->getAttributesByTable() as $table => $attributes) {
                $this->_getWriteAdapter()->delete($table, $where);
            }
        } catch (Exception $e) {
            throw $e;
        }

        $this->_afterDelete($object);
        return $this;
    }

After going through save and load process, the delete process is understood automatically. No need go through each function