Magento 2 Where (code) are the filters from the layered navigation applied to the product collection?

The question:

I’m trying to find the portion of code where the filters from the layered navigation are applied to the product collection.

The method called for product collection retrival is MagentoCatalogBlockProductListProduct::_getProductCollection

Which in turn calls MagentoCatalogModelLayer::getProductCollection this uses a collection provider to instantiate the collection and then MagentoCatalogModelLayer::prepareProductCollection().

The layer object above contains the filters selected in the layered navigation on frontend and above metioned function (prepare) uses MagentoCatalogModelLayerCategoryCollectionFilter::filter.

As stated in the function associated comment this should filter the collection * Filter product collection yet I see no filters from the layered navigation applied.

I still can not find where the filters from the layered navigation are applied.

I should mention that the MagentoCatalogBlockProductListProduct::_getProductCollection is called in the MagentoCatalogBlockProductListProduct::_beforeToHtml method at then end of this method the filtered collection is loaded but even going with the debugger line by line I still can not find the portion of code that applies the filters.

In this method an 'catalog_block_product_list_collection' event is dispatched. Is it possible that the filters are applied in an Observer?

EDIT:

The only observer tapped to this event is MagentoReviewObserverCatalogBlockProductCollectionBeforeToHtmlObserver and it does not seem to be related to my problem.

Edit 2:

The product collection used in MagentoCatalogBlockProductLitProduct
is extended from MagentoCatalogSearchModelResourceModelFulltextCollection which has a method _renderFiltersBefore which is called when the collection is loaded. Here the filters are fed and set through a searchCriteriaBuilder the search is made and put in a temporary table which is joined with the productCollection.

I still can not find the actual piece of code that retrieves and handles the filters.

Any help is appreciated.

Many thanks.

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

I found the answer for this. (I think)
While it makes sense at some level. The flow is kinda messed up.
Here it goes:

As I stated in my question the collection used to display the products is a descendant of MagentoCatalogSearchModelResourceModelFulltextCollection that uses a SearchCriteriaBuilder to perform the actual search.

Still when debugging the actual display of the products I could not find any place where the filters were added. But in the _renderFiltersBefore() (method that is called before loading the collection) when the searchCriteriaBuilder was retrieved the filters were already there.

The searchCriteriaBuilder is retrieved using the objectManager::get() which implements a singleton pattern.

Long story short. The same collection(object type not instance) is used for the navigation block. And when rendering it the filters are applied here MagentoLayeredNavigationBlockNavigation::_prepareLayout()

This are two separate blocks, working with different instances. But because of the singleton approach in retrieving the searchCriteriaBuilder when the ListProduct block loads the collection the filters are already set.

Basically what they’ve done (from what I can tell) is coupled the shit out of this two blocks.

  1. The layered navigation block is rendered and here the filters are applied.
  2. When rendering the Product List they “know” that the filters were already applied and go ahead with loading the collection.

I find this to be messed up on so many levels. If someone can prove me wrong. Please do.

Method 2

Filters are added to searchCriteriaBuilder by the addFieldToFilter() function.

For example in MagentoCatalogSearchModelLayerFilterAttribute:

public function apply(MagentoFrameworkAppRequestInterface $request)
{
    $attributeValue = $request->getParam($this->_requestVar);
    if (empty($attributeValue) && !is_numeric($attributeValue)) {
        return $this;
    }

    $attribute = $this->getAttributeModel();
    /** @var MagentoCatalogSearchModelResourceModelFulltextCollection $productCollection */
    $productCollection = $this->getLayer()
        ->getProductCollection();
    $productCollection->addFieldToFilter($attribute->getAttributeCode(), $attributeValue);

    $labels = [];
    foreach ((array) $attributeValue as $value) {
        $label = $this->getOptionText($value);
        $labels[] = is_array($label) ? $label : [$label];
    }
    $label = implode(',', array_unique(array_merge(...$labels)));
    $this->getLayer()
        ->getState()
        ->addFilter($this->_createItem($label, $attributeValue));

    //$this->setItems([]); // set items to disable show filtering
    return $this;
}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment