Magento – Standard Way to Load Source Model of Product Attributes

eavproduct-attributesource-model

I have a product attribute with type "select", for example "manufacturer". No source model is defined, so Magento uses the default source model, defined in Mage_Eav_Model_Entity:

const DEFAULT_SOURCE_MODEL      = 'eav/entity_attribute_source_config';

But with this code:

$manufacturerAttribute = Mage::getModel('catalog/entity_attribute')
    ->loadByCode(Mage_Catalog_Model_Product::ENTITY, 'manufacturer');
$values = $manufacturerAttribute->getSource()->getAllOptions();

I get this exception:

Source model "" not found for attribute "manufacturer"

Tracing down the error, I found that the default source model is actually fetched in Mage_Eav_Model_Entity_Attribute_Abstract with:

protected function _getDefaultSourceModel()
{
    return $this->getEntity()->getDefaultAttributeSourceModel();
}

and:

/**
 * Retrieve entity instance
 *
 * @return Mage_Eav_Model_Entity_Abstract
 */
public function getEntity()
{
    if (!$this->_entity) {
        $this->_entity = $this->getEntityType();
    }
    return $this->_entity;
}

This looks all fine in the IDE but when I debug, I notice that getEntity actually returns an object of Mage_Eav_Model_Entity_Type which does not have a getDefaultAttributeSourceModel() method, unlike Mage_Eav_Model_Entity_Abstract. Looks like a bug in Magento to me…

What makes it even more confusing, is that _getDefaultSourceModel() is overridden in Mage_Catalog_Model_Resource_Eav_Attribute:

/**
 * Get default attribute source model
 *
 * @return string
 */
public function _getDefaultSourceModel()
{
    return 'eav/entity_attribute_source_table';
}

But since $attribute->getResource() always gives me an instance of Mage_Eav_Model_Entity_Attribute, I don't know how to make use of this.

I know that I could define the source model explicitly in an update script and that's how I usually handle it, but there has to be a proper way to make use of the default source model, I just can't figure it out.

Best Answer

Not exactly an answer, but a little long winded to be a comment...

I'd have to agree, that this is indeed a bug, or perhaps it should be framed as an 'unfinished feature'. As such I don't personally believe there is a 'proper way'. Since saving a new attribute model will set the source_model field (in Mage_Eav_Model_Resource_Entity_Attribute::_beforeSave) it should be impossible to create a new attribute without a source_model. Therefore it's arguably a bug in Mage_Eav_Model_Entity_Setup::_insertAttribute, which creates rows in the database directly, allowing for empty values in that column.

Another interesting observation is that getDefaultAttributeSourceModel returns eav/entity_attribute_source_config, whereas the value injected by _beforeSave is eav/entity_attribute_source_table (which falls inline with the default you saw in the catalog attribute). Comparing the two models, the former is actually pretty useless in itself. The docblock for the class points out that it should really be abstract, which seems to be the case. Unless 2 protected members (_options and _configNodePath) have a value, the class will always throw an exception Failed to load node %s from config. The latter however will look in the table eav_attribute_option_value, which sounds much more useful.

The closest I can get to an answer aside from 'fixing' the database, is to manually do what the _beforeSave would have done:

$manufacturerAttribute = Mage::getModel('catalog/entity_attribute')
    ->loadByCode(Mage_Catalog_Model_Product::ENTITY, 'manufacturer');
if ( ! $manufacturerAttribute->getSourceModel()) {
    $manufacturerAttribute->setSourceModel('eav/entity_attribute_source_table')
}
$values = $manufacturerAttribute->getSource()->getAllOptions();

Apologies if there's any false syllogisms here, it's getting late.

Related Topic