Magento2 – Product Collection Observer Only Changes One Page at a Time

collection;event-observermagento2.4magento2.4.0product-collection

In ListProduct block Magento is using initializeProductCollection() to initialize the collection.

While initializing it dispatches event catalog_block_product_list_collection as follows.

$this->_eventManager->dispatch(
    'catalog_block_product_list_collection',
    ['collection' => $collection]
);

Using the event catalog_block_product_list_collection I am trying to filter a list of products:

$_collection = $observer->getCollection();
$_collection->addAttributeToFilter('sku', ['nin' => $invalidSkus]);

however instead of limiting the category from 50 products to 49 products, it is limiting the page results of 12 products to 11 products. If I have a list of 12 invalid skus that all coincide to be on page 1, then page 1 returns no products.

How do I filter the entire product list for a category, not just the product list for a single category page?

Best Answer

The product IDs for this one category are coming from elasticsearch based on the page size defined in "Store > Settings > Configuration > Catalog > Catalog > Storefront > Products per Page on Grid Default Value". If you navigate to the next page, Magento requests the next 12 product IDs from Elasticsearch and these will be included in the SQL query that creates the collection.

This is how Magento has been designed, otherwise, if the query contains all the product that has been assigned to the category, there will be a performance impact.

What this means, is that the event catalog_block_product_list_collection is far too late to be adding or removing products to the list. The logic to do this must be placed where Elasticsearch is query is being built.

The solution is to create a plugin for the elasticsearch client and edit the query there:

vendor/module/etc/di.xml

<type name="Magento\Elasticsearch7\Model\Client\Elasticsearch">
    <plugin name="extend_elasticsearch" type="Vendor\Module\Plugin\Elasticsearch" sortOrder="1"/>
</type>

vendor/module/Plugin/Elasticsearch.php

namespace Vendor\Module\Plugin;

class Elasticsearch
{
    public function beforeQuery($subject,$query) {
        //Do not include product ids of the invalid skus in search result.
        $productIdsToExclude = [1,2,3];
        $query['body']['query']['bool']['must_not'] = [
            'ids' => [ 'values' => $productIdsToExclude]
        ];
        return [$query];
    }
}
Related Topic