Introduction

Sometime we need to load Category List while developing something for Magento 2.

And there are a few ways to do it.

But in this small chapter we will use the right way.

Solution

The right way to load Category List includes two things:

  1. use CategoryListInterface to get the list of categories

  2. use Search Criteria to build a query

Here is the basic code to load Category List. This code will load All Categories, becuase we are using empty Search Criteria.

<?php

declare(strict_types=1);

namespace Vendor\ModuleName\ViewModel;

use Magento\Catalog\Api\CategoryListInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;

class SomeClass
{
    /**
     * @var CategoryListInterface
     */
    private CategoryListInterface $categoryList;

    /**
     * @var SearchCriteriaBuilder
     */
    private SearchCriteriaBuilder $searchCriteriaBuilder;

    /**
     * @apram CategoryListInterface $categoryList
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     */
    public function __construct(
        CategoryListInterface $categoryList,
        SearchCriteriaBuilder $searchCriteriaBuilder
    )
    {
        $this->categoryList = $categoryList;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
    }

    public function apply()
    {
        $searchCriteria = $this->searchCriteriaBuilder->create();
        $categoryList = $this->categoryList->getList($searchCriteria);

        foreach ($categoryList->getItems() as $category) {
            /**
             * Here you can do with the $category
             * the things you want.
             *
             * For example:
             * 
             *   if (!$category->getIsActive()) {
             *     continue;
             *   }
             *
             * Or
             *
             *   $category->setStoreId(0);
             *   $category->setAvailableSortBy(['price']);
             *
             */
        }
    }
}

Load Category List filtered by params

To do it we should use a few new components like:

  1. FilterBuilder
  2. SortOrderBuilder

Let’s look at the code and then I will explain what happens here.

<?php

declare(strict_types=1);

namespace Vendor\ModuleName\ViewModel;

use Magento\Catalog\Api\CategoryListInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Api\SortOrderBuilder;

class SomeClass
{
    /**
     * @var CategoryListInterface
     */
    private CategoryListInterface $categoryList;

    /**
     * @var SearchCriteriaBuilder
     */
    private SearchCriteriaBuilder $searchCriteriaBuilder;

    /**
     * @var FilterBuilder
     */
    private FilterBuilder $filterBuilder;

    /**
     * @var SortOrderBuilder
     */
    private SortOrderBuilder $sortOrderBuilder;

    /**
     * @apram CategoryListInterface $categoryList
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param FilterBuilder $filterBuilder
     * @param SortOrderBuilder $sortOrderBuilder
     */
    public function __construct(
        CategoryListInterface $categoryList,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        FilterBuilder $filterBuilder,
        SortOrderBuilder $sortOrderBuilder
    )
    {
        $this->categoryList = $categoryList;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        $this->filterBuilder = $filterBuilder;
        $this->sortOrderBuilder = $sortOrderBuilder;
    }

    public function apply()
    {
        $filter1 = $this->filterBuilder->setField('level')
            ->setValue(3)
            ->setConditionType('eq')
            ->create();
        $filter2 = $this->filterBuilder->setField('children_count')
            ->setValue(200)
            ->setConditionType('gt')
            ->create();
        $sortOrder = $this->sortOrderBuilder->setField('entity_id')
            ->setDirection('DESC')
            ->create();
        $this->searchCriteriaBuilder->addFilters([$filter1, $filter2]);
        $this->searchCriteriaBuilder->addSortOrder($sortOrder);
        $searchCriteria = $this->searchCriteriaBuilder->create();

        $categoryList = $this->categoryList->getList($searchCriteria);

        foreach ($categoryList->getItems() as $category) {
            /**
             * Here you can do with the $category
             * the things you want.
             *
             * For example:
             * 
             *   if (!$category->getIsActive()) {
             *     continue;
             *   }
             *
             * Or
             *
             *   $category->setStoreId(0);
             *   $category->setAvailableSortBy(['price']);
             *
             */
        }
    }
}

As you can see this code is similar to the basic code in the Solution section.

The only filters and sort order was added.

I think the $filter’s variables are clear to you. Buuut… let’s take a look at $filter1, for example.

  1. First, we set field name by which we want to filter on.
->setField('level')
  1. Then we set the value of the field by which we want to filter.
->setValue(3)
  1. And last step is to add condition type
->setConditionType('eq')

This filter reads as: “I want to select Categories with the level equals (eq) to the 3.”

Then we add sort order by the entity_id field in the DESC direction.

$sortOrder = $this->sortOrderBuilder->setField('entity_id')
    ->setDirection('DESC')
    ->create();
****
$this->searchCriteriaBuilder->addSortOrder($sortOrder);

It was simple and clear things. But …

The most interesting thing is this line of code:

$this->searchCriteriaBuilder->addFilters([$filter1, $filter2]);

If you navigate to the \Magento\Catalog\Model\CategoryList::getList method and check what query is generated to get list of categories, you will see something like

SELECT `e`.* FROM `catalog_category_entity` AS `e`
WHERE ((`e`.`level` = 3) OR (`e`.`children_count` > 200)) 
ORDER BY `e`.`entity_id` DESC

As you can see in the WHERE part our filters are generated using OR statement.

So, if you put array of Filters into the addFilters method - all of these filters will be combined with OR statement.

But if you want to use AND statement, you should use addFilter method of SearchCriteriaBuilder object.

In our example we will replace the line:

$this->searchCriteriaBuilder->addFilters([$filter1, $filter2]);

with

$this->searchCriteriaBuilder->addFilter([$filter1]);
$this->searchCriteriaBuilder->addFilter([$filter2]);

And now the sql query would be:

SELECT `e`.* FROM `catalog_category_entity` AS `e`
WHERE ((`e`.`level` = 3)) AND ((`e`.`children_count` > 200))
ORDER BY `e`.`entity_id` DESC

Also you can use addFilter method of SearchCriteriaBuilder object to get WHERE clause with the OR statement.

Read more about Search Criteria and how to configure it in the official Magento page.