Have a look at how it's been done in the layered navigation:
the responsible method is Mage_Catalog_Model_Resource_Layer_Filter_Attribute::getCount()
:
/**
* Retrieve array with products counts per attribute option
*
* @param Mage_Catalog_Model_Layer_Filter_Attribute $filter
* @return array
*/
public function getCount($filter)
{
// clone select from collection with filters
$select = clone $filter->getLayer()->getProductCollection()->getSelect();
// reset columns, order and limitation conditions
$select->reset(Zend_Db_Select::COLUMNS);
$select->reset(Zend_Db_Select::ORDER);
$select->reset(Zend_Db_Select::LIMIT_COUNT);
$select->reset(Zend_Db_Select::LIMIT_OFFSET);
$connection = $this->_getReadAdapter();
$attribute = $filter->getAttributeModel();
$tableAlias = sprintf('%s_idx', $attribute->getAttributeCode());
$conditions = array(
"{$tableAlias}.entity_id = e.entity_id",
$connection->quoteInto("{$tableAlias}.attribute_id = ?", $attribute->getAttributeId()),
$connection->quoteInto("{$tableAlias}.store_id = ?", $filter->getStoreId()),
);
$select
->join(
array($tableAlias => $this->getMainTable()),
join(' AND ', $conditions),
array('value', 'count' => new Zend_Db_Expr("COUNT({$tableAlias}.entity_id)")))
->group("{$tableAlias}.value");
return $connection->fetchPairs($select);
}
Unfortunately you can't use that straight away with any collection because it's too tightly coupled to the filter model, which again doesn't handle arbitrary product collections.
So your best option is probably to copy what you need from there into an own resource model like this (untested):
class Your_Extension_Model_Resource_Attribute extends Mage_Core_Model_Resource_Db_Abstract
{
public function getCount($productCollection, $attributeCode, $storeId)
{
// clone select from collection with filters
//----------------------------------------------------------------
$select = clone $productCollection->getSelect();
//----------------------------------------------------------------
// reset columns, order and limitation conditions
$select->reset(Zend_Db_Select::COLUMNS);
$select->reset(Zend_Db_Select::ORDER);
$select->reset(Zend_Db_Select::LIMIT_COUNT);
$select->reset(Zend_Db_Select::LIMIT_OFFSET);
$connection = $this->_getReadAdapter();
//----------------------------------------------------------------
$attribute = Mage::getModel('eav/entity_attribute')->loadByCode($attributeCode);
//----------------------------------------------------------------
$tableAlias = sprintf('%s_idx', $attribute->getAttributeCode());
$conditions = array(
"{$tableAlias}.entity_id = e.entity_id",
$connection->quoteInto("{$tableAlias}.attribute_id = ?", $attribute->getAttributeId()),
//----------------------------------------------------------------
$connection->quoteInto("{$tableAlias}.store_id = ?", $storeId),
//----------------------------------------------------------------
);
$select
->join(
array($tableAlias => $this->getMainTable()),
join(' AND ', $conditions),
array('value', 'count' => new Zend_Db_Expr("COUNT({$tableAlias}.entity_id)")))
->group("{$tableAlias}.value");
return $connection->fetchPairs($select);
}
}
And then call it like
$options = Mage::getResourceModel('your_extension/attribute')
->getCount($collection, 'color', Mage::app()->getStore()->getId());
$options
will be an array with the values as key and number of occurances as value. Filtering out the 0's should not be a problem from there.
While all of the above answers are correct, I have also found that if you want to just update one option, you can use a shorthand version:
No need to update all the array of options, just set the one you like, like this
Here is an example adding an option to an attribute with values:
//option id = 42
//store value => beer
//default value => Beer
//2nd store value => Bière (french)
//load attribute as in other examples
$attribute = $attribute_model->load($attribute_code);
$option_id = 42; // NUMERIC VALUE
$opt_default_name='beer';
$opt_default_store = 'Beer';
$opt_2nd_store = 'Biére';
$attribute->setData('option', array('value'=> array(
$option_id => array ($opt_default_name, $opt_default_store, $opt_2nd_store));
)));
$attribute->save();
This will just modify the option "beer" for the default and second store (ok. the example is quick, just check the others for correctly getting store IDs..)
Best Answer
You can create 2 plugins for after
load
method for collection and product.Plugin declaration
app/code/Acme/StackExchange/etc/di.xml
Product Model Plugin
app/code/Acme/StackExchange/Plugin/Catalog/Model/ProductPlugin.php
Product Collection Plugin
app/code/Acme/StackExchange/Plugin/Catalog/Model/ResourceModel/Product/CollectionPlugin.php