Search code examples
shopware6

Exclude specific products from Product Indexer in Shopware 6


We have four specific products with a massive amount of variants. When running the Product Indexer we run out of memory because of these products. So we want to exclude these specific products from the Product Indexer Job.

My first approach was to use the ProductIndexerEvent, but the event is dispatched at the end of the handle() method : (vendor/shopware/core/Content/Product/DataAbstractionLayer/ProductIndexer.php:187),

which is probably too late.

What is the best approach to implement that behaviour?


Solution

  • I would advise against excluding products from being indexed. There's business logic relying on the data being indexed.

    If you're confident in what you're doing and know about the consequences, you could decorate the ProductIndexer service.

    <service id="Foo\MyPlugin\ProductIndexerDecorator" decorates="Shopware\Core\Content\Product\DataAbstractionLayer\ProductIndexer">
        <argument type="service" id="Foo\MyPlugin\ProductIndexerDecorator.inner"/>
    </service>
    

    In the decorator you would have to deconstruct the original event, filter the WriteResult instances by excluded IDs and then pass the reconstructed event to the decorated service.

    class ProductIndexerDecorator extends EntityIndexer
    {
        const FILTERED_IDS = ['9b180c61ddef4dad89e9f3b9fa13f3be'];
    
        private EntityIndexer $decorated;
    
        public function __construct(EntityIndexer $decorated)
        {
            $this->decorated = $decorated;
        }
    
        public function getDecorated(): EntityIndexer
        {
            return $this->decorated;
        }
    
        public function getName(): string
        {
            return $this->getDecorated()->getName();
        }
    
        public function iterate($offset): ?EntityIndexingMessage
        {
            return $this->getDecorated()->iterate($offset);
        }
    
        public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage
        {
            $originalEvents = clone $event->getEvents();
    
            if (!$originalEvents) {
                return $this->getDecorated()->update($event);
            }
    
            $event->getEvents()->clear();
    
            /** @var EntityWrittenEvent $writtenEvent */
            foreach ($originalEvents as $writtenEvent) {
                if ($writtenEvent->getEntityName() !== 'product') {
                    $event->getEvents()->add($writtenEvent);
    
                    continue;
                }
    
                $results = [];
                foreach ($writtenEvent->getWriteResults() as $result) {
                    if (\in_array($result->getPrimaryKey(), self::FILTERED_IDS, true)) {
                        continue;
                    }
    
                    $results[] = $result;
                }
    
                $event->getEvents()->add(new EntityWrittenEvent('product', $results, $event->getContext()));
            }
    
            return $this->getDecorated()->update($event);
        }
    
        public function handle(EntityIndexingMessage $message): void
        {
            $data = array_diff($message->getData(), self::FILTERED_IDS);
    
            $newMessage = new ProductIndexingMessage($data, $message->getOffset(), $message->getContext(), $message->forceQueue());
    
            $this->getDecorated()->handle($newMessage);
        }
    
        public function getTotal(): int
        {
            return $this->getDecorated()->getTotal();
        }
    
        public function getOptions(): array
        {
            return $this->getDecorated()->getOptions();
        }
    }