Magento – Category collection: addAttributeToFilter on parents

categorycollection;magento-1.9

I have a category structure like this with a total of around 5000 categories:

  • A is_active=1
    • A1 is_active=1
      • A1-1 is_active=1
      • A1-2 is_active=1
      • A1-…
    • A2
    • A3
  • B is_active=0
    • B1 is_active=1
      • B1-… is_active=1
    • B2
    • B3

Now, getting all active categories is quite easy:

/** @var \Mage_Catalog_Model_Resource_Category_Collection $categories */
            $categories = Mage::getResourceModel('catalog/category_collection');

            $categories->addAttributeToSelect('*');
            $categories->addAttributeToFilter('entity_id', ['neq' => $this->getCurrentCategory()->getId()]);
            $categories->addAttributeToFilter('is_active', 1);


            return $categories;

I obviously also get all the B1 subcategories; how do I remove those from the returned collection? In general: I want to remove all the categories with a disabled parent from the collection. My hunch is to make a loop, but I suspect there is a smarter solution.

Best Answer

Yes, there are smarter solutions that perform better: First of all, each category has an attribute "path" that could be used to filter things in a smart way. You can add a filter like this:

    $categories->addAttributeToFilter('path', array('like' => '1/3/%');

The path includes the ID 1 (left part in example) and the Root Catalog of your Store (3 in the example).

An alternative is to create the other collection first ($excludeCategories) and convert them to a list of IDs:

$excludeIds = $excludeCategories->getAllIds();

Next, you can use these IDs to exclude from the original collection:

$categories->addAttributeToFilter('entity_id', array('nin', $excludeIds));

In the example above, "nin" stands for "NOT IN".

EDIT: Based on the suggestion of @Amasty I'm happy to share a bit more direct solution. If you want to prevent any category from being loaded that has a parent that is inactive, the goal is first to find all those parents:

$inactive = Mage::getModel('catalog/category')->getCollection();
$inactive->addAttributeToFilter('is_active', 0);
$inactiveIds = $inactive->getAllIds();

Next, with this list of inactive parents ($inactiveIds) you can find all categories that do not have those inactive parents in their paths:

$categories = Mage::getModel('catalog/category')->getCollection();
$categories->addAttributeToFilter('is_active', 1);
foreach($inactiveIds as $inactiveId) {
    $categories->addAttributeToFilter('path', array('nlike' => '%/'.$inactiveId.'/%'));
}

This worked for me in a demo Magento environment. However, I'm not sure how well this is going to perform with 5000 categories. It simply depends on how many of these categories are inactive and how many are not. Most likely you will want to have the output of things cached properly.