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
Design a custom indexer that enriches product data with computed fields from external API data.
Quiz
1. What interface must a custom indexer implement?
2. What XML file defines custom indexers?
3. What method reindexes all data in a custom indexer?
4. What is the productType attribute in indexer.xml used for?
Flashcards
Question
What interface do custom indexers implement?
Click to reveal answer
Answer
Magento\Framework\Indexer\ActionInterface
Question
What file defines custom indexers?
Click to reveal answer
Answer
indexer.xml in module's etc/ directory
Question
What three methods does ActionInterface require?
Click to reveal answer
Answer
executeFull(), executeList(array $ids), executeRow($id)
Question
How to check custom indexer status?
Click to reveal answer
Answer
php bin/magento indexer:status vendor_custom_indexer
Question
What table stores index state?
Click to reveal answer
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