Execution Flows
Step-by-step traces through product save, product load, category operations, price calculation, and indexer execution flows.
Step-by-step traces through product save, product load, category operations, price calculation, and indexer execution flows.
The ProductRepositoryInterface::save() method is the primary entry point for persisting product data. This flow handles validation, EAV attribute storage, media gallery, and triggers indexer invalidation.
Receives ProductInterface object. Validates SKU uniqueness if creating new product.
Magento\Catalog\Model\ProductRepository::save($product, $saveOptions = false)
Plugins intercept before persisting. Key plugins include:
InventoryPlugin: Reserve stockCategoryLinkPlugin: Process category assignmentsUrlRewritePlugin: Generate URL rewritesThe core persistence layer saves entity and all EAV attribute values.
// Transaction starts INSERT INTO catalog_product_entity (sku, type_id, attribute_set_id, ...) INSERT INTO catalog_product_entity_varchar (entity_id, attribute_id, store_id, value) INSERT INTO catalog_product_entity_int (entity_id, attribute_id, store_id, value) INSERT INTO catalog_product_entity_decimal (entity_id, attribute_id, store_id, value) // ... for each attribute type // Transaction commits
Observers process post-save logic:
ImageResizeAfterProductSave: Queue image resizeIndexerPlugin: Invalidate affected indexesFullPageCache: Flush product cache tagsIf indexers are in "Update on Schedule" mode, changelogs are updated. Otherwise, partial reindex runs.
Saving products with many attributes or media gallery images can be slow. Use $saveOptions = true to skip validation for bulk imports. Always batch save operations and consider disabling indexers during large imports.
Loading a product via ProductRepositoryInterface::get() or getById() retrieves the entity and all its EAV attributes based on the current store scope.
Check instance cache first. If not cached, proceed to load.
// Instance cache check
if (isset($this->instances[$sku][$storeId])) {
return $this->instances[$sku][$storeId];
}
Multiple JOINs retrieve all attribute values for the entity:
SELECT e.*,
t_varchar.value AS name,
t_decimal.value AS price,
t_int.value AS status
FROM catalog_product_entity e
LEFT JOIN catalog_product_entity_varchar t_varchar
ON e.entity_id = t_varchar.entity_id
AND t_varchar.attribute_id = 73 /* name */
AND t_varchar.store_id IN (0, 1)
LEFT JOIN catalog_product_entity_decimal t_decimal
ON e.entity_id = t_decimal.entity_id
AND t_decimal.attribute_id = 77 /* price */
-- ... repeated for each attribute
After entity load, extension attributes are hydrated via ReadExtensions:
Observers can modify the loaded product. Entity is then cached and returned.
Loading products in a loop causes N+1 query issues. Always use ProductCollectionFactory with addAttributeToSelect() for batch loading. The Flat Catalog avoids EAV JOINs for frontend queries.
Category save is similar to product save but includes tree path recalculation and children_count updates.
catalog_category_prepare_savecatalog_category_save_beforecatalog_category_save_afterclean_cache_by_tags (FPC)Price calculation is one of the most complex flows in Catalog. The final price depends on base price, special price, tier prices, catalog rules, customer group, and tax configuration.
Read price attribute from EAV or indexed price table.
If special_price is set and within special_from_date / special_to_date, use it.
Check catalog_product_entity_tier_price for quantity-based discounts by customer group.
Query catalogrule_product_price for applicable rule discounts.
Lowest applicable price is returned. For display, tax may be added based on configuration.
$product->getPrice() returns the BASE price attribute only. $product->getFinalPrice() runs the full price calculation. This is a common source of bugs. Always use getFinalPrice() for customer-facing prices.
Indexers transform EAV data into flat, query-optimized tables. They can run in "Update on Save" (immediate) or "Update on Schedule" (cron-based) modes.
# Reindex all catalog indexers
bin/magento indexer:reindex catalog_product_flat catalog_category_flat \
catalog_category_product catalog_product_price catalog_product_attribute
# Check indexer status
bin/magento indexer:status
When a customer visits a product page, the request flows through controller, block, and template layers with full-page cache optimization.
Controller: Magento\Catalog\Controller\Product\View
ProductViewHelper::prepareAndRender() loads product with design settings.
Layout handles applied in order:
Key blocks: product.info, product.info.price, product.info.media, product.info.options
Response cached with tags: cat_p_{id}, cat_c_{category_ids}. Hole-punching for private data (cart, customer).