Magento filter product collection by multiple categories

25,799

Solution 1

The way Magento works now, is to get the Store, and on the store, you can get the categories from the storecollection like $oStoreCollection->addCategoryFilter(array('1','2'));

I came across a solution that might help you, found here at:

http://www.magentocommerce.com/boards/&/viewthread/201114/#t329230

The code they use, looks like this: Override Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection, and add the following methods:

public function addCategoriesFilter($categories)
    {
        $this->_productLimitationFilters['category_ids'] = $categories;

        if ($this->getStoreId() == Mage_Core_Model_App::ADMIN_STORE_ID) {
            $this->_applyZeroStoreProductLimitations();
        } else {
            $this->_applyProductLimitations();
        }

        return $this;
    }

    protected function _applyProductLimitations()
    {
        $this->_prepareProductLimitationFilters();
        $this->_productLimitationJoinWebsite();
        $this->_productLimitationJoinPrice();
        $filters = $this->_productLimitationFilters;

        // Addition: support for filtering multiple categories.
        if (!isset($filters['category_id']) && !isset($filters['category_ids']) && !isset($filters['visibility'])) {
            return $this;
        }

        $conditions = array(
            'cat_index.product_id=e.entity_id',
            $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id'])
        );
        if (isset($filters['visibility']) && !isset($filters['store_table'])) {
            $conditions[] = $this->getConnection()
                ->quoteInto('cat_index.visibility IN(?)', $filters['visibility']);
        }

        // Addition: support for filtering multiple categories.
        if (!isset($filters['category_ids'])) {
             $conditions[] = $this->getConnection()
                ->quoteInto('cat_index.category_id=?', $filters['category_id']);
            if (isset($filters['category_is_anchor'])) {
                $conditions[] = $this->getConnection()
                    ->quoteInto('cat_index.is_parent=?', $filters['category_is_anchor']);
            }
        } else {
            $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id IN(' . implode(',', $filters['category_ids']) . ')', "");
        }

        $joinCond = join(' AND ', $conditions);
        $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM);
        if (isset($fromPart['cat_index'])) {
            $fromPart['cat_index']['joinCondition'] = $joinCond;
            $this->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart);
        }
        else {
            $this->getSelect()->join(
                array('cat_index' => $this->getTable('catalog/category_product_index')),
                $joinCond,
                array('cat_index_position' => 'position')
            );
        }

        $this->_productLimitationJoinStore();

        Mage::dispatchEvent('catalog_product_collection_apply_limitations_after', array(
            'collection'    => $this
        ));

        return $this;
    }

    protected function _applyZeroStoreProductLimitations()
    {
        $filters = $this->_productLimitationFilters;

        // Addition: supprot for filtering multiple categories.
        $categoryCondition = null;
        if (!isset($filters['category_ids'])) {
            $categoryCondition = $this->getConnection()->quoteInto('cat_pro.category_id=?', $filters['category_id']);
        } else {
            $categoryCondition = $this->getConnection()->quoteInto('cat_pro.category_id IN(' . implode(',', $filters['category_ids']) . ')', "");
        }

        $conditions = array(
            'cat_pro.product_id=e.entity_id',
            $categoryCondition
        );
        $joinCond = join(' AND ', $conditions);

        $fromPart = $this->getSelect()->getPart(Zend_Db_Select::FROM);
        if (isset($fromPart['cat_pro'])) {
            $fromPart['cat_pro']['joinCondition'] = $joinCond;
            $this->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart);
        }
        else {
            $this->getSelect()->join(
                array('cat_pro' => $this->getTable('catalog/category_product')),
                $joinCond,
                array('cat_index_position' => 'position')
            );
        }

        return $this;
    }

It then gets called like this:

$collection = Mage::getModel('catalog/product')->getCollection()
                        ->addAttributeToSelect('*')
                        ->distinct(true) // THIS IS WHAT YOU NEED TO ADD
                        ->addCategoriesFilter($category->getAllChildren(true)); // Make sure you don't forget to retrieve your category here.

HTH

Solution 2

Here's a method that doesn't require modifications to core. It's taken from this post with the addition of the 'group' clause to handle duplicate product records.

$categories = array(7,45,233);

        $collection = Mage::getModel('catalog/product')->getCollection()
            ->addAttributeToSelect('*')
            ->joinField('category_id',
                'catalog/category_product',
                'category_id',
                'product_id=entity_id',
                null,
                'left')
            ->addAttributeToFilter('category_id', array('in' => $categories));
        $collection->getSelect()->group('e.entity_id');

Solution 3

If you want to filter on multiple categories, using AND (so a product must be in categorie A, B and C to show up, you need to have multiple joins:

$products = Mage::getModel('catalog/product')->getCollection()
    ->joinField('category_id_1', 'catalog/category_product', 'category_id', 'product_id=entity_id', null, 'left')
    ->joinField('category_id_2', 'catalog/category_product', 'category_id', 'product_id=entity_id', null, 'left')
    ->addAttributeToFilter('category_id_1', array('eq' => 358))
    ->addAttributeToFilter('category_id_2', array('eq' => 252))
// etc...
;

Solution 4

I managed to resolve this (after much trial and error) with the following code:

$collection = Mage::getModel('catalog/product')->getCollection();
$collection->addAttributeToFilter('status', 1);
$collection->addAttributeToSelect(array('name','sku','price','small_image'));

// Filter by multiple categories
$collection->joinField('category_id','catalog/category_product','category_id','product_id=entity_id',null,'left');
$data_cats = $this->getRequest()->getParam('categories');
// Or $data_cats = array(85,86,87,88);

      $filter_cats = array();
      foreach ($data_cats as $value_cats) {
         $filter_cats[] = array(
         'attribute' => 'category_id',
         'finset'    => $value_cats
      );
}

$collection->addAttributeToFilter($filter_cats);

Hope this helps someone ;)

Share:
25,799
Alex Hadley
Author by

Alex Hadley

I mainly develop websites, spending most of my time building Magento eCommerce stores. I also spent 5 years as an IT Manager. If you need any Magento development - on existing or new sites, or indeed any PHP or web design work generally, drop me a line. I also have degrees in Mathematics, Philosophy and Economics, though I'm not quite sure why...

Updated on September 12, 2021

Comments

  • Alex Hadley
    Alex Hadley over 2 years

    Is there an easy way to filter a product collection by multiple categories? To get all items in any of the listed categories? addCategoryFilter doesn't seem to allow an array.

    Is the only way to get the collections for each category of interest separately then merge them?

    I understand it used to be possible with something like

    addAttributeToFilter('category_ids',array('finset'=>array('1','2')))
    

    or similar, but that this is no longer possible since 1.4.

    Note: I am using 1.6, and in case it's of any use, I'm using something like this:

    $product = Mage::getModel('catalog/product');
    $_productCollection = $product->getCollection()
      ->addAttributeToSelect('*')
      ->addAttributeToFilter('status',1)
      ->addStoreFilter();
    
  • Alex Hadley
    Alex Hadley about 12 years
    Thanks for that. I've added both, and now get an unknown method error. For some reason it's not recognising what's been added, any thoughts?
  • Alex Hadley
    Alex Hadley about 12 years
    Fatal error: Call to undefined method Mage_Catalog_Model_Resource_Product_Collection::addCategorie‌​sFilter()....
  • Alex Hadley
    Alex Hadley about 12 years
    Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.ph‌​p just doesn't seem to be being loaded at all
  • ShaunOReilly
    ShaunOReilly about 12 years
    did you actually extend Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection?
  • Alex Hadley
    Alex Hadley about 12 years
    yes, using the almost blank Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Collection.ph‌​p as a starting point
  • Alex Hadley
    Alex Hadley about 12 years
    I ended up finding an alternative solution by filtering on attribute_set_id instead. I'll mark this correct as it seems to have worked for some in the forum too.
  • dangvy
    dangvy about 12 years
    I'm looking to implement this so I can have filtering by multiple categories on the category page. So I'm on Manufacturer > ManufName page and then I filter by another category e.g. Type > Widgets but I'm unclear where the product collection for category pages get built. Don't suppose you have an idea? TIA.
  • ShaunOReilly
    ShaunOReilly about 12 years
    @dangvy, I think you should post this as a question, as it is slightly different, and will need a different solution.
  • Buttle Butkus
    Buttle Butkus over 11 years
    The join is working but the filtering is not. Nothing is added to the WHERE clause.
  • Jim OHalloran
    Jim OHalloran over 10 years
    I've tried this solution as well and ran into the same issue as @ButtleButkus ... I see the join is added to the generated query, but no WHERE condition. We're using Magento EE 1.12 (aka CE 1.7).
  • Jim OHalloran
    Jim OHalloran over 10 years
    I've been stepping through the code, and this solution would work when Magento's flat catalog is turned off. When flat catalog is enabled the following occurs. The addAttributeToFilter method recognises that the category_id field is not in the SELECT list, and calls addAttributeToSelect to add it. The addAttributeToSelect method then fails because category_id is not a product attribute, and no filter is added to the collection.
  • ShaunOReilly
    ShaunOReilly over 10 years
    Ensure you clear the Magento cache after all this work guys, so that it will properly load the new code.
  • Patrick Steenks
    Patrick Steenks over 10 years
    Thanks for explaining the group clause, I was looking for a similar solution but did not understand how it worked. No I do!
  • Alex
    Alex about 7 years
    @JimOHalloran I experienced the exact opposite. It only works if flat catalog is turned on.