Creating EAV based model(s) in Magento

eav_magento_3

Magento EAV (Entity Attribute Value) data model is used to get flexibility for your data, but it brings more complexity than relation table model. If you need data model that will have flexible attributes which can be dynamically added, for example from Magento admin panel, then EAV is the best solution for you. If using EAV, you don’t need to change table structure for every new attribute like you do on flat tables (creating new colums).

Bad things which EAV flexibility brings are slower Queries and more complex table structures. Let’s talk more about performance, EAV system is slower than using flat table for resource model, because it uses a lot of mysql joins (depend on attributes number) and, as we know, Query with join data from other tables is always slower than select Query from one table. This problem can be solved with two solutions. For Enterprise Magento enable full page cache and page will be cached, so with or without EAV there won’t be any difference, only first load(slower for EAV) and after that all is cached. Second solution is to make flat table and indexer for creating table from EAV entity attributes.

After short brief about good and bad things with EAV let’s see how to create custom EAV model in example. For demonstration purposes let’s create Blog module with EAV resource model for posts, and focus will be on creating model, resource model and collection for post with installation. Admin part for adding new posts and custom attributes won’t be part of this article because they’re not required for our EAV model to work. I will demonstrate how to use EAV resurce model and Collection to get post(s) on frontend.

First we need to create our Blog module by adding module declaration in config, for this we’ll create Inchoo_Blog.xml in app\etc\modules\:
app\etc\modules\Inchoo_Blog.xml:

<?xml version="1.0"?>
<!--
Inchoo Blog Module
Author: Zoran Šalamun(zoran.salamun@inchoo.net)
https://inchoo.net/
-->
<config>
    <modules>
        <Inchoo_Blog>
            <active>true</active>
            <codePool>local</codePool>
        </Inchoo_Blog>
    </modules>
</config>

After registering our module, next step is to create folder structure for Module (Block, Helper, controllers, etc, Model and sql folders in module root folder – app\code\local\Inchoo\Blog\), and inside etc folder config.xml for module configuration:

<?xml version="1.0"?>
<!--
    Blog - Module for testing custom EAV tables
    Author: Zoran Šalamun(zoran.salamun@inchoo.net)
-->
<config>
    <modules>
        <Inchoo_Blog>
            <version>1.0.0.0</version>
        </Inchoo_Blog>
    </modules>
    <global>
        <models>
            <inchoo_blog>
                <class>Inchoo_Blog_Model</class>
            </inchoo_blog>
        </models>
        <helpers>
            <inchoo_blog>
                <class>Inchoo_Blog_Helper</class>
            </inchoo_blog>
        </helpers>
        <blocks>
            <inchoo_blog>
                <class>Inchoo_Blog_Block</class>
            </inchoo_blog>
        </blocks>
    </global>
</config>xxx

In config file we add common options by declaring module version, models, helpers, blocks. After adding them, we add other config options needed for our module to work with entity tables. In config file we have declared model class so Magento can know what model to use with Mage::getModel.

For Blog posts we need to create post model, post resource model and post collection. First we’ll create post model in app/code/local/Inchoo/Blog/Model/ with file name Post.php:

class Inchoo_Blog_Model_Post extends Mage_Core_Model_Abstract
{
 
}

Note that Inchoo_Blog_Model_Post class extends Mage_Core_Model_Abstract, same as every other Model, but EAV magic starts in resource model. In post model class we need to define our resource model in _construct():

protected function _construct()
    {
        $this->_init('inchoo_blog/post');
    }

inchoo_blog/post is resource model that need to be declared in our config.xml and we’ll add it now in models node:

<models>
            <inchoo_blog>
                <class>Inchoo_Blog_Model</class>
                <resourceModel>inchoo_blog_resource</resourceModel>
            </inchoo_blog>
            <inchoo_blog_resource>
                <class>Inchoo_Blog_Model_Resource</class>
            </inchoo_blog_resource>
        </models>

inchoo_blog is model node name and post is file name for resource model. Magento creates path to resource model joining inchoo_blog resource class (Inchoo_Blog_Model_Resource) with resource model name (Post). After Magento join this from configuration we’ll get post resource class name (and path): Inchoo_Blog_Model_Resource_Post.

Now it’s time to create our resource model, so let’s create Post.php file in app/code/local/Inchoo/Blog/Model/Resource/ folder:

class Inchoo_Blog_Model_Resource_Post extends Mage_Eav_Model_Entity_Abstract
{
 
}

As you can see, our resurce model Inchoo_Blog_Model_Resource_Post extends Mage_Eav_Model_Entity_Abstract and this is how Magento handle EAV in resource model. To make this work with Entity tables we need to set options and default attributes, so to set options we’ll add this code:

public function __construct()
    {
        $resource = Mage::getSingleton('core/resource');
        $this->setType('inchoo_blog_post');
        $this->setConnection(
            $resource->getConnection('blog_read'),
            $resource->getConnection('blog_write')
        );
    }

As you can see, we need to set resource and connection. For connection read and write we need to add in our module config file nodes where we define read and write connections:

<resources>
            <blog_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </blog_write>
            <blog_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </blog_read>
        </resources>

$this->setType (‘inchoo_blog_post’) in our Post resource model init defines what entity type resource should be used. We’ll define this later in our module install script. Also in resource model we need to specify default attributes in _getDefaultAttributes() method:

protected function _getDefaultAttributes()
    {
        return array(
            'entity_type_id',
            'attribute_set_id',
            'created_at',
            'updated_at',
            'increment_id',
            'store_id',
            'website_id'
        );
    }

These are default attributes that will be loaded, but we’ll get back on them later. They’re not required to set because Mage_Eav_Model_Entity_Abstract already set them. You can do it only if you want to change default attributes.

After creating our resource model we need to create collection class, and we need to create it in app/code/local/Inchoo/Blog/Model/Resource/Post folder with file called Collection.php and that class has to extend EAV collection class abstract:

class Inchoo_Blog_Model_Resource_Post_Collection extends Mage_Eav_Model_Entity_Collection_Abstract
{
 
    protected function _construct()
    {
        $this->_init('inchoo_blog/post');
    }
 
}

We have extended our collection class Inchoo_Blog_Model_Resource_Post_Collection with Mage_Eav_Model_Entity_Collection_Abstract and this gives us options for our resource model to work with EAV tables. As you can see, you need to set resource model like on any other collection ($this->_init(‘inchoo_blog/post’)).

After setting our model, resource model and collection we have created all functionalities that make this work with EAV. But, one more important step is required to make this actually work – create entity tables and add entity type. We’ll do this with our install script, first we need to set this in our config file:

<resources>
            <inchoo_blog_setup>
                <setup>
                    <module>Inchoo_Blog</module>
                    <class>Inchoo_Blog_Model_Resource_Setup</class>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </inchoo_blog_setup>
        </resources>

Now we need to create two things, as you can see in configuration, we need Inchoo_Blog_Model_Resource_Setup class for our setup and setup file for version 1.0.0.0 like our version of module is. Setup class is needed for specifying what attributes model can save in entity tables, and we’ll use this class in our setup script. Let’s create our setup class in app/code/local/Inchoo/Blog/Model/Resource/:

<?php
 
class Inchoo_Blog_Model_Resource_Setup extends Mage_Eav_Model_Entity_Setup
{
    /*
     * Setup attributes for inchoo_blog_post entity type
     * -this attributes will be saved in db if you set them
     */
    public function getDefaultEntities()
    {
        $entities = array(
            'inchoo_blog_post' => array(
                'entity_model' => 'inchoo_blog/post',
                'attribute_model' => '',
                'table' => 'inchoo_blog/post_entity',
                'attributes' => array(
                    'title' => array(
                        'type' => 'varchar',
                        'backend' => '',
                        'frontend' => '',
                        'label' => 'Title',
                        'input' => 'text',
                        'class' => '',
                        'source' => '',
                        'global' => 0,
                        'visible' => true,
                        'required' => true,
                        'user_defined' => true,
                        'default' => '',
                        'searchable' => false,
                        'filterable' => false,
                        'comparable' => false,
                        'visible_on_front' => true,
                        'unique' => false,
                    ),
                    'author' => array(
                        'type' => 'varchar',
                        'backend' => '',
                        'frontend' => '',
                        'label' => 'Author',
                        'input' => 'text',
                        'class' => '',
                        'source' => '',
                        'global' => 0,
                        'visible' => true,
                        'required' => true,
                        'user_defined' => true,
                        'default' => '',
                        'searchable' => false,
                        'filterable' => false,
                        'comparable' => false,
                        'visible_on_front' => false,
                        'unique' => false,
                    ),
                ),
            )
        );
        return $entities;
    }
}

You can see that our setup class extends Mage_Eav_Model_Entity_Setup and we’ve defined our entities and attributes. For entity is used inchoo_blog_post and that’s our entity type. This needs to be declared in our setup script and we’ve set it also in our post resource model. This is important step because we must set our attributes for entity model so our resource model can save them in entity tables.

For example, if our blog should have an author, we need to specify author attribute in getDefaultEntities method under our inchoo_blog_post entity attributes. If you don’t set attribute and use save on post model, Magento won’t know where to save data and your attribute won’t be saved. Good thing with attributes is that they’re only saved in tables, if you set them, but if you don’t set (for example author) on post and save, it will not save this attribute. This approach saves db space in comparison with flat table, as we know, flat table will have all attributes as columns and that columns have NULL value. And that is the biggest advantage of using EAV, especially if you have a lot attributes which are in most cases set for some blog posts.

One more important advantage in using EAV is to have attributes that will work for different languages(stores), you can see example of this in category/product EAV. When comparing flat table vs EAV, EAV advantages are that flat for some attributes can have mostly NULL values, and if you use EAV all attributes that have not been set won’t be saved anywhere – they will be saved only if you set them.

Lastly, for our post model to work, we need to make setup script for creating tables and to add entity type with attributes. If you check setup for catalog/product or customer, you can see that every table for EAV is created separately by installer. This approach is good if you want some additional columns in main entity table, if you don’t need that than create EntityTables, this method is best to use because it will automatically create all EAV tables (note that in 1.6 and 1.7 users reported bugs with this functionality). For our EAV to work we need main entity table (_entity at end of table name) and 6 additional tables for every data type to be saved. For example, every time you create a new blog post a new entity will be added to main entity table, and attributes to entity specific type tables (for example varchar type attribute to table that ends with _entity_varchar) if you’ve set attribute. Every attribute is new entry in specific attribute type table, so if you have set 5 attributes there will be 5 entries times stores(if you have 3 stores, it will be 15 entries – 5 attributes x 3 stores) in entity tables.

We have defined setup script with defining setup class and now it’s time to define our tables. To do that, we only need to define main entity table name, but for demonstration we’ll define all entity tables to demonstrate how to create all entity tables by creating them separately. In configuration file under inchoo_blog_resources node we’ll add tables configuration:

<inchoo_blog_resource>
                <class>Inchoo_Blog_Model_Resource</class>
                <entities>
                    <post_entity>
                        <table>inchoo_blog_post_entity</table>
                    </post_entity>
                    <post_entity_datetime>
                        <table>inchoo_blog_post_entity_datetime</table>
                    </post_entity_datetime>
                    <post_entity_decimal>
                        <table>inchoo_blog_post_entity_decimal</table>
                    </post_entity_decimal>
                    <post_entity_int>
                        <table>inchoo_blog_post_entity_int</table>
                    </post_entity_int>
                    <post_entity_text>
                        <table>inchoo_blog_post_entity_text</table>
                    </post_entity_text>
                    <post_entity_varchar>
                        <table>inchoo_blog_post_entity_varchar</table>
                    </post_entity_varchar>
                    <post_entity_char>
                        <table>inchoo_blog_post_entity_char</table>
                    </post_entity_char>
                </entities>
            </inchoo_blog_resource>

For creating tables with createEntityTables we only need post_entity definition and to create all entity tables in install script with createTable, we need all other table definitions.

Now we’ll create install script, for our version (1.0.0.0), name for install file will be install-1.0.0.0.php. First, we’ll create install script that uses createEntityTables:

$installer = $this;
$installer->startSetup();
 
/*
 * Create all entity tables
 */
$installer->createEntityTables(
    $this->getTable('inchoo_blog/post_entity')
);
 
/*
 * Add Entity type
 */
$installer->addEntityType('inchoo_blog_post',Array(
    'entity_model'          =>'inchoo_blog/post',
    'attribute_model'       =>'',
    'table'                 =>'inchoo_blog/post_entity',
    'increment_model'       =>'',
    'increment_per_store'   =>'0'
));
 
$installer->installEntities();
 
$installer->endSetup();

So, we have set installer, $this refers to our setup class (we have defined that in config file). After setting the installer we have created tables with createEntityTables. This method has created following tables:

inchoo_blog_post_entity
inchoo_blog_post_entity_datetime
inchoo_blog_post_entity_decimal
inchoo_blog_post_entity_int
inchoo_blog_post_entity_text
inchoo_blog_post_entity_varchar
inchoo_blog_post_entity_char

inchoo_blog_post_entity is main entity table, and 6 other tables are data type specific for attributes.

After creating tables we added new entity type with addEntityType, so let’s explain params:

entity_model – our model which will be used for entity (must have resource that extends Mage_Eav_Model_Entity_Abstract)
attribute_model – if you want to use other attribute model, remember that attribute models are not limited to 6 standard models and you can create your own
table – our main entity table
increment_model – model responsible for generating increment IDs
increment_per_store – use increment ID per store(1)

Last thing in install script is to install our entities with installEntities(), which we have defined in setup class (getDefaultEntities). Entity type is added to eav_entity_type table and this is Magento core table for entity types.

I want to show you code for install script to create all entity tables manually:

<?php
 
$installer = $this;
$installer->startSetup();
 
/**
 * Create table 'inchoo_testeav/entity'
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity'))
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
        'unsigned'  => true,
     ), 'Entity Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Set Id')
    ->addColumn('increment_id', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array(
        'nullable'  => false,
        'default'   => '',
    ), 'Increment Id')
    ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Store Id')
    ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
        'nullable'  => false,
    ), 'Created At')
    ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
        'nullable'  => false,
    ), 'Updated At')
    ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '1',
    ), 'Defines Is Entity Active')
    ->addIndex($this->getIdxName($baseTableName, array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($this->getIdxName($baseTableName, array('store_id')),
        array('store_id'))
    ->addForeignKey($this->getFkName($baseTableName, 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $this->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($this->getFkName($baseTableName, 'store_id', 'core/store', 'store_id'),
        'store_id', $this->getTable('core/store'), 'store_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Post Entity Main Table');
$installer->getConnection()->createTable($table);
 
/*
 * Datetime entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_datetime'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array(
        'nullable'  => false,
        'default' => $installer->getConnection()->getSuggestedZeroDate()
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog_post_entity_datetime',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'),
        array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog_post_entity_datetime', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey(
        $installer->getFkName('inchoo_blog_post_entity_datetime', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog_post_entity_datetime', 'entity_id', 'inchoo_blog/post_entity_datetime', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity_datetime'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName(
            'inchoo_blog_post_entity_datetime',
            'entity_type_id',
            'eav/entity_type',
            'entity_type_id'
        ),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Datetime');
$installer->getConnection()->createTable($table);
 
/*
 * Decimal entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_decimal'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_DECIMAL, '12,4', array(
        'nullable'  => false,
        'default'   => '0.0000',
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_decimal',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_decimal', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_decimal', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_decimal', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog/post_entity_decimal', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Decimal');
$installer->getConnection()->createTable($table);
 
/*
 * Integer entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_int'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'nullable'  => false,
        'default'   => '0',
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_int',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_int', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_int', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_int', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_int', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Int');
$installer->getConnection()->createTable($table);
 
/*
 * Text entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_text'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array(
        'nullable'  => false,
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_text',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_text', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_text', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_text', array('entity_id')),
        array('entity_id'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_text', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_text', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog/post_entity_text', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Text');
$installer->getConnection()->createTable($table);
 
/*
 * Varchar entity table for blog post
 */
$table = $installer->getConnection()
    ->newTable($installer->getTable('inchoo_blog/post_entity_varchar'))
    ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'identity'  => true,
        'nullable'  => false,
        'primary'   => true,
    ), 'Value Id')
    ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Type Id')
    ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Attribute Id')
    ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
        'unsigned'  => true,
        'nullable'  => false,
        'default'   => '0',
    ), 'Entity Id')
    ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array(
    ), 'Value')
    ->addIndex(
        $installer->getIdxName(
            'inchoo_blog/post_entity_varchar',
            array('entity_id', 'attribute_id'),
            Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
        ),
        array('entity_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('entity_type_id')),
        array('entity_type_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('attribute_id')),
        array('attribute_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('entity_id')),
        array('entity_id'))
    ->addIndex($installer->getIdxName('inchoo_blog/post_entity_varchar', array('entity_id', 'attribute_id', 'value')),
        array('entity_id', 'attribute_id', 'value'))
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_varchar', 'attribute_id', 'eav/attribute', 'attribute_id'),
        'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey($installer->getFkName('inchoo_blog/post_entity_varchar', 'entity_id', 'customer/entity', 'entity_id'),
        'entity_id', $installer->getTable('inchoo_blog/post_entity'), 'entity_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->addForeignKey(
        $installer->getFkName('inchoo_blog/post_entity_varchar', 'entity_type_id', 'eav/entity_type', 'entity_type_id'),
        'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id',
        Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
    ->setComment('Blog Post Entity Varchar');
$installer->getConnection()->createTable($table);
 
/*
 * Add Entity type
 */
$installer->addEntityType('inchoo_blog_post',Array(
    'entity_model'          =>'inchoo_blog/post',
    'attribute_model'       =>'',
    'table'                 =>'inchoo_blog/post_entity',
    'increment_model'       =>'',
    'increment_per_store'   =>'0'
));
 
$installer->installEntities();
 
$installer->endSetup();

You can see that we removed createEntityTables and defined (also created) all tables manually. This approach can give us some additional columns specific for our needs, and for blog we can create title and content columns. As we know, every blog post needs to have title and content, so there is no need to use attributes for them.

Testing our code

For testing purposes we’ll create a route definition and index controller. Let’s define everything in config.xml

<frontend>
        <routers>
            <inchoo_blog>
                <use>standard</use>
                <args>
                    <module>Inchoo_Blog</module>
                    <frontName>blog</frontName>
                </args>
            </inchoo_blog>
        </routers>
    </frontend>

As you can see, defined in frontName we’ll use /blog/ for our blog, and for test we’ll use index controller and index action. Url for testing will be http:://ourmagento.com/blog/ or http:://ourmagento.com/blog/index/index/ (if we only write /blog/ magento will know that index is controller and index is action if not defined otherwise).

For testing we’ll now create index controller with index action:

class Inchoo_Blog_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        /*
         * Create new blog post with author and title
         * -this will create new row in inchoo_blog_post_entity table and
         *  two entries for title and author attributes will be saved in inchoo_blog_post_entity_varchar table
         */
        $post = Mage::getModel('inchoo_blog/post');
        $post->setTitle('Test title');
        $post->setAuthor('Zoran Šalamun');
        $post->save();
 
        /*
         * Try to create post with book type attribute
         * -book type attribute will not be saved because book type attribute is not defined for our entity type
         * -on new row will be added in inchoo_blog_post_entity
         */
        $post = Mage::getModel('inchoo_blog/post');
        $post->setBookType('Test title');
        $post->save();
 
        /*
         * Getting posts collection
         * -also load collection
         * -this will load all post entries but without attributes
         * -loaded data is only from inchoo_blog_post_entity table
         */
        $posts = Mage::getModel('inchoo_blog/post')->getCollection();
        $posts->load();
        var_dump($posts);
 
        /*
         * Getting post collection
         * -load all posts
         * -set attributs to be in collection data
         */
        $posts = Mage::getModel('inchoo_blog/post')->getCollection()
                ->addAttributeToSelect('title')
                ->addAttributeToSelect('author');
        $posts->load();
        var_dump($posts);
 
        /*
         * Load signle post
         * -loading single post will get all attributes that we have set for post
         */
        $post = Mage::getModel('inchoo_blog/post')->load(1);
        var_dump($post);
    }
}

I hope this will help you understand basics for creating EAV models. Also, you can download test module which we’ve used for demonstration here or you can find it on GitHub.


About Zoran Salamun

Backend Developer

Zoran strives to do his best in everything he attempts. He likes to play PC games, but since he doesn’t know when to stop, it always ends up with deleting them.

Read more posts by Zoran / Visit Zoran's profile

13 comments

  1. Hi,
    This is an awesome tutorial, but the only thing I wanted to ask you is, how do I associate this custom entity with a product? So that, when I fetch any product, the values of my custom entity should also appear.

    Thanks in advance.

  2. I tried the same but saw one issue. My tables are getting created with a mix of Abstract and my model resource’s default attributes. it ignored website id but added store id to the default list of columns. Do you have any idea why this should happen?

  3. nice explanation. But one thing is missing. each value table (_int, _varchar, ….) need to have a unique index on the fields combo `attribute_id`, `store_id`, `entity_id` otherwise it will not work. Instead of updating the values for one attribute of an entity in a store view, it will insert a new row in the table.

  4. Do you have any idea how can i make the custom attribute as unique for the create account. I already created a customer attribute ‘mobile_no’ and shown it on create account page. Now i want this to be unique.

  5. Hi.

    Great post!! Thanks!!

    I have only one doubt… I have created an unique attribute called “email” in getDefaultEntities() – Company_Name_Entity_Setup – .

    In database, the column “is_required” has value “1”, but when i load model and submit save() action, it´s possible to insert multiple items with the same attribute value. I´m using Magento CE 1.9.

    Tks again

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.