Skip to content
intermediate Phase 67 · Indexer System

Custom Indexers — Creating Your Own Indexer

Creating custom indexers in Magento 2: IndexerInterface, data providers, indexer configuration, and implementing custom indexing logic

1h
1 problems
Topic Progress 0%

Custom Indexer Structure

Indexer.xml Configuration

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd">
    <indexer id="vendor_product_enrichment"
             class="Vendor\Module\Model\Indexer\ProductEnrichment"
             productType="catalog_product"
             label="Product Enrichment Index"
             description="Custom index for product enrichment data"/>
</config>

Indexer Class

namespace Vendor\Module\Model\Indexer;

use Magento\Framework\Indexer\ActionInterface;
use Magento\Framework\Indexer\IndexerRegistry;

class ProductEnrichment implements ActionInterface
{
    public function __construct(
        private IndexerRegistry $indexerRegistry,
        private DataProvider $dataProvider,
        private IndexWriter $indexWriter
    ) {}

    public function executeFull()
    {
        $this->indexWriter->clearIndex();
        $items = $this->dataProvider->getAllItems();
        foreach ($items as $item) {
            $this->indexRow($item->getId());
        }
    }

    public function executeList(array $ids)
    {
        foreach ($ids as $id) {
            $this->indexRow($id);
        }
    }

    public function executeRow($id)
    {
        $data = $this->dataProvider->getItemData($id);
        $this->indexWriter->saveIndex($id, $data);
    }
}

Indexer Interface Methods

Method Description
executeFull() Reindex all data
executeList(array $ids) Reindex specific IDs
executeRow($id) Reindex single item

Data Providers

Data Provider Class

namespace Vendor\Module\Model\Indexer;

use Magento\Framework\DB\Select;

class DataProvider
{
    public function __construct(
        private \Magento\Framework\App\ResourceConnection $resource
    ) {}

    public function getAllItems(): array
    {
        $connection = $resource->getConnection();
        $select = $connection->select()->from('catalog_product_entity');
        return $connection->fetchAll($select);
    }

    public function getItemData(int $id): array
    {
        $connection = $resource->getConnection();
        $select = $connection->select()
            ->from('catalog_product_entity')
            ->where('entity_id = ?', $id);
        return $connection->fetchRow($select);
    }

    public function getItemsByIds(array $ids): array
    {
        $connection = $resource->getConnection();
        $select = $connection->select()
            ->from('catalog_product_entity')
            ->where('entity_id IN (?)', $ids);
        return $connection->fetchAll($select);
    }
}

EAV Data Provider

namespace Vendor\Module\Model\Indexer;

class EavDataProvider
{
    public function getItemData(int $id): array
    {
        $product = $this->productFactory->create()->load($id);
        return [
            'entity_id' => $product->getId(),
            'sku' => $product->getSku(),
            'name' => $product->getName(),
            'price' => $product->getPrice(),
            'custom_data' => $this->computeCustomData($product)
        ];
    }
}

Index Writer and Table

Index Writer

namespace Vendor\Module\Model\Indexer;

class IndexWriter
{
    public function __construct(
        private \Magento\Framework\App\ResourceConnection $resource
    ) {}

    public function clearIndex(): void
    {
        $connection = $resource->getConnection();
        $connection->truncateTable('vendor_product_enrichment_index');
    }

    public function saveIndex(int $id, array $data): void
    {
        $connection = $resource->getConnection();
        $connection->insertOnDuplicate(
            'vendor_product_enrichment_index',
            array_merge(['entity_id' => $id], $data)
        );
    }

    public function deleteIndex(int $id): void
    {
        $connection = $resource->getConnection();
        $connection->delete(
            'vendor_product_enrichment_index',
            ['entity_id = ?' => $id]
        );
    }
}

Index Table Schema

<!-- db_schema.xml -->
<table name="vendor_product_enrichment_index" resource="default" engine="innodb">
    <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false"/>
    <column xsi:type="varchar" name="sku" nullable="false" length="255"/>
    <column xsi:type="varchar" name="enriched_name" nullable="true" length="500"/>
    <column xsi:type="decimal" name="computed_price" scale="4" precision="12" unsigned="false" nullable="true"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
        <column name="entity_id"/>
    </constraint>
</table>

Testing Custom Indexers

CLI Testing

# Check indexer appears
php bin/magento indexer:info

# Check status
php bin/magento indexer:status vendor_product_enrichment

# Full reindex
php bin/magento indexer:reindex vendor_product_enrichment

# Partial reindex
php bin/magento indexer:reindex vendor_product_enrichment 101 102

Unit Test

namespace Vendor\Module\Test\Unit\Model\Indexer;

use PHPUnit\Framework\TestCase;
use Vendor\Module\Model\Indexer\ProductEnrichment;

class ProductEnrichmentTest extends TestCase
{
    public function testExecuteRow(): void
    {
        $indexer = $this->createMock(ProductEnrichment::class);
        $indexer->expects($this->once())
            ->method('executeRow')
            ->with(101);
        
        $indexer->executeRow(101);
    }
}

Integration Test

namespace Vendor\Module\Test\Integration\Model\Indexer;

use Magento\TestFramework\Helper\Bootstrap;

class ProductEnrichmentTest extends TestCase
{
    public function testFullReindex(): void
    {
        $indexer = Bootstrap::getObjectManager()
            ->get(\Magento\Framework\Indexer\IndexerRegistry::class)
            ->get('vendor_product_enrichment');
        
        $indexer->reindexFull();
        
        $this->assertEquals('valid', $indexer->getState()->getStatus());
    }
}

Debug Commands

# Check index data
mysql -u root -p -e "SELECT COUNT(*) FROM vendor_product_enrichment_index;"

# Check indexer state
mysql -u root -p -e "SELECT * FROM indexer_state WHERE indexer_id = 'vendor_product_enrichment';"

# Reset indexer
php bin/magento indexer:reset vendor_product_enrichment

Practice Problems

0 / 1 solved
Custom Indexer Design

Design a custom indexer that enriches product data with computed fields from external API data.

Quiz

1. What interface must a custom indexer implement?

Question 1 options

2. What XML file defines custom indexers?

Question 2 options

3. What method reindexes all data in a custom indexer?

Question 3 options

4. What is the productType attribute in indexer.xml used for?

Question 4 options

Flashcards

Question

What interface do custom indexers implement?

Answer

Magento\Framework\Indexer\ActionInterface

Question

What file defines custom indexers?

Answer

indexer.xml in module's etc/ directory

Question

What three methods does ActionInterface require?

Answer

executeFull(), executeList(array $ids), executeRow($id)

Question

How to check custom indexer status?

Answer

php bin/magento indexer:status vendor_custom_indexer

Question

What table stores index state?

Answer

indexer_state with columns indexer_id and status

Revision Notes

Key Takeaways

  • 1. Custom indexers implement ActionInterface with executeFull, executeList, executeRow
  • 2. indexer.xml defines the indexer with id, class, label, and description
  • 3. Data providers fetch source data for indexing
  • 4. Index writers save computed data to custom index tables
  • 5. db_schema.xml defines custom index table structure
  • 6. Test with CLI commands and unit/integration tests

Interview Tips

  • Explain the ActionInterface methods and when each is called
  • Describe how to create data providers for custom indexers
  • Discuss testing strategies for custom indexers
  • Know the difference between executeFull, executeList, and executeRow

Cheat Sheet

Custom Indexers Cheat Sheet

indexer.xml:

<indexer id="vendor_custom" class="Vendor\Indexer"
          label="Custom Index" description="..."/>

Interface:

implements ActionInterface {
    executeFull()    // Reindex all
    executeList($ids) // Reindex specific
    executeRow($id)   // Reindex one
}

Table:
db_schema.xml

Test:
php bin/magento indexer:reindex vendor_custom

Debug:

  • indexer:status vendor_custom
  • SELECT from indexer_state