This is a tip from Vitaly Korotun in his presentation at the Magento Developers Paradise on using custom backend models to customize Magento. I thought I’d expand on his tip with a worked example for my own benefit – but you may find it useful too!
I think this was a bit of an Aha! moment at the conference as everyone in the room gave sideways looks to Vitaly’s answer to his own question: “How would we implement custom functionality to integrate with a third party system every time product data changes”. I thought I already knew the answer, or at least all of the possible answers. But Vitaly had a good one, that I didn’t think of.
You can see the answers on slide 40/43 of the presentation. Here are my thoughts on each, the last one is the one I hadn’t thought of, and judging by the ajar mouths in the conference room, a lot of other developers hadn’t thought of it either!
- Override the controller
saveAction() - Override the Product model
save()method (or the resource model save) - Declare an Observer for the
saveevent - Using a custom backend model for an attribute.
This is not a very good solution as it will only run the customized functionality when the model is saved from the controller (through the browser for example). It wouldn’t run if the model was saved from the API though which in most cases will be a problem. This might be a suitable solution if it is desirable functionality for the 3rd party update to only occur when the model is updated from through the controller though, so consider it an option in some cases for very specific requirements.
This is probably the most common solution, override the model and implement functionality in the save method. It works no matter how the object is saved, but is open to extension clash hell which is not appealing.
(I’ve grouped these two together, because they’re mostly the same)
This is one of the solutions I suggested in my own presentation when using extensions for customized functionality that avoids clashes. It can help to avoid the clash issues and allows pinpoint customizing with eitherbeforeSave or afterSave events. So a great solution for the problem posed by Vitaly and the one I think most developers, myself included, considered the best.
Vitaly suggested using a customized backend model for a particular product attribute as the solution. This was a new one to me, though in hindsight I see it’s a technique already in use in a number of places in Magento core code, I’d just not noticed until someone pointed out the obvious. I’ll look at this technique in more detail below, following it up with a worked example.
Benefits and difficulties with Vitaly’s solution
There are a number of benefits with the solution of a custom backend model on an attribute. It means the custom functionality exists at the attribute level. As we know attributes can be added/removed from products easily in Magento. That means to apply the 3rd party integration on all products of a particular type, we would simply add the attribute to the attribute set for that type of product. This gives us the flexibility to integrate only for certain product types too.
This sort of flexibility to add the attribute onto only some products can be really useful. For example, if a store sold Digital cameras and SD cards, then perhaps they’d only put the audit_check attribute on the digital camera attribute set, so only when digital cameras are being modified will an email get sent to the manager to audit the changes (perhaps they don’t care who changes the price of SD cards).
It’s also less prone to clashes as even if someone else is overriding the save functionality or listening for the event, they won’t affect the save function of your attribute. Particularly on a model as central as the Product.
One of the hassles is it requires that the attribute be added to the product’s attribute set in order to function. This can be done manually by the store owner in the admin interface, or in a script from within your extension. If you specify the attribute is a system attribute when you create it, then it will be automatically added to all attribute sets in the system, and the attribute cannot be removed via the admin interface (nor can it be deleted). We’ll look at how this can be done in the next section.
Worked Example: An audit_check attribute
For this example we’ll create an attribute that when applied to a product, will cause any changes made to that product to be logged for audit. This allows a store manager to keep a tabs on what products are being changed and in what ways, for any attribute sets which contain the audit_check attribute. Update: this is now an actual extension – if you want to see a working example.
The Attribute class
First we define the attribute and decide if the logic should run beforeSave or afterSave. Here’s a trivial example of an afterSave backend model.
class Aschroder_ProductAudit_Model_Entity_Attribute_Backend_Audit extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract {
public function afterSave($object) {
// This is where we would perform the audit (or any other logic we wanted)
// We could email the changes to a store admin, for example
}
}
Installing the attribute
Now we need to install the attribute and decide if it should be a system attribute or not. There are a few steps involved here. If you need a full tutorial on creating setup scripts for your extensions, check out Alan Storm’s excellent guide. I’ll assume you know that and won’t explain it again here. (One word of warning: Do make sure your setup is a subclass of Mage_Eav_Model_Entity_Setup)
Adding a system attribute
Adding a system attribute will ensure the attribute is added to all sets in the system, and that it cannot be removed.
// This attribute will get installed in all attribute sets
$installer->addAttribute('catalog_product', 'audit_check',
array(
'type' => 'varchar',
'label' => 'Reason for Change',
'class' => '',
'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE,
'backend' => 'productaudit/entity_attribute_backend_audit',
'visible' => true,
'required' => true,
'user_defined' => false, // this is what determines if it will be a system attribute or not
'default' => '',
'searchable' => false,
'filterable' => false,
'comparable' => false,
'visible_on_front' => false,
'unique' => false
));
If you want to see how this system attribute is applied to all sets, check out function addAttribute in Mage_Eav_Model_Entity_Setup. You’ll notice this little snippet which adds the attribute to all the sets:
if (empty($attr['user_defined'])) {
$sets = $this->_conn->fetchAll('select * from '.$this->getTable('eav/attribute_set').' where entity_type_id=?', $entityTypeId);
foreach ($sets as $set) {
$this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], $this->_generalGroupName, $code, $sortOrder);
}
}
Note: If you’re like me, you might develop a slight nervous twitch when you see PHP code that uses the empty() function to test if something is true or false (false is hardly empty, it’s false, right?) – apparently it’s totally kosher, relax!
You can also use the ‘group‘ property on the attribute to assign the attribute to a particular group for every attribute set (and add the group).
Adding a non-system attribute
Non-system attributes will not be added to any attribute sets by default. However you can add them to sets manually or automatically as in the next section.
The only difference is the setting of user_defined to true like so:
// This attribute will get installed in no attribute sets by default
$installer->addAttribute('catalog_product', 'audit_check_non_system',
array(
'type' => 'varchar',
'label' => 'Reason for Change',
'class' => '',
'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_STORE,
'backend' => 'productaudit/entity_attribute_backend_audit',
'visible' => true,
'required' => true,
'user_defined' => true, // this is what determines if it will be a system attribute or not
'default' => '',
'searchable' => false,
'filterable' => false,
'comparable' => false,
'visible_on_front' => false,
'unique' => false
));
Associating the attribute to Products
If you chose to make the attribute user defined (i.e non-system) in the previous section, then you either have to add it to an attribute set from within your script, or the store admin would have to do so manually from within the admin interface. Otherwise, it’s just an attribute floating in space, not associated to any product. Let’s look at both of these options:
Adding the attribute to a set in your script
When you create a non-system attribute you’ll have to add it to a set automatically. You can optionally create your own group automatically too.
// Add a new group (these are the sections in the admin interface)
// You can just use General - in which case you do not need to add a new group
$installer->addAttributeGroup('catalog_product', 'Default', 'Audit');
//Add the attribute to the group (implicitly adds it to the set too)
$installer->addAttributeToGroup ('catalog_product', 'Default', 'Audit', 'audit_check_non_system');
The result will look like this in the attribute set admin screen:

New Audit Group and Attribute added to it
And like this when editing a product in the Default attribute set:

Product with Audit Group
Adding the attribute to a set from within the admin interface
If you add the attribute as part of your customizations but then do not assign it to a set, it allows store managers to choose which attribute sets use the attribute. This is done through the Catalog->Attributes->Manage Attribute Sets menu, you simple drag and drop the attribute onto your desired group, as shown in the screenshots below:


Easy eh!
Conclusion
It’s a bit of a cop-out I know, but I’m going to say that all of the solutions have their strengths and weaknesses, you should use the solution that best solves the problem at hand. In the most part, the overriding of a model or listening for an event will be easy, and fairly safe. It’s nice to know there is another option available to us though. It takes a fair bit more work to implement, but gives a flexible solution – thanks to Vitaly for sharing it.
Originally published on magebase.com. Copyright © 2010 Magebase - All Rights Reserved.




Proud members of the
Great post! I really like the attribute models system of magento.
Just wanted to point out that this of course also works on any models with an eav resource model, including custom ones.
Other related topics would be frontend, source and attribute models.
Yes you’re right, I should probably have mentioned that – thanks. You can add attributes to any entity type by just changing the code here when adding the attribute:
$installer->addAttribute(‘catalog_product’,…
to
$installer->addAttribute(‘your_entity_type_here’,…
Powerful functionality for sure.
[...] wrote up an article over on MageBase this weekend expanding on a tip I got from the Magento Developers Paradise. In the process of writing it, I went through a worked example and as usual with articles you want [...]
Thanks Ashley, good write-up. I’ve always used the Observer model (except there are a few circumstances where the core code doesn’t throw an Event at the right time – grrr) and never thought of this approach.
One question though, does the beforeSave() or afterSave() method on the attribute get called each time that the product is saved, or only when that attribute changes? That would limit its effectiveness for updating remote systems cos I can’t think of a way to force the user to change the value each time?? Thoughts?
Cheers,
JD
Great tutorial. I want to know what would be the type for Drop Down attribute, as for the text area you have assigned ‘varchar’. and from where can I get the complete list?
thanks
You can use varchar or int (depending on the value type of the options).
For multi-selects use varchar since the values are stored as a comma-separated list (be sure to use the eav/entity_attribute_backend_array backend model).
For a list of all types run
SELECT DISTINCT backend_type FROM eav_attribute;
Thank you very much Vinai Kopp
to understand custom model, you can look at this tutorial : http://www.about-magento.com/magento-admin-gridview-85
I have a question for you guys, can I programatically change the $_backend for an attributte, during runtime? Meaning, i want for an attribute like media gallery who has _backend something like Product/Attribute/Backend/Media, to temporarily change that to another class (and then maybe switch back to the original). Any idea if how this is possible?
gr8. hopefully now i can fix eav/attribute_data_checkbox issue, i am having.