Skip to content

Execution Flows

Attribute loading, entity save, attribute set assignment, and option value resolution.

Critical Flow

1. Entity Load with EAV Attributes

Loading an EAV entity requires joining multiple value tables to retrieve all attribute values. This flow shows how a product or customer entity is loaded with its EAV data.

[1] Repository::getById($entityId)
|
[2] AbstractEntity::load($object, $entityId)
|
[3] Load static attributes from entity table
| SELECT * FROM catalog_product_entity WHERE entity_id = ?
|
[4] AttributeLoader::loadAllAttributes($entityType)
| // Get all attributes for this entity type (cached)
|
[5] For each backend_type (varchar, int, decimal, text, datetime):
|
+-->
AbstractEntity::_getLoadAttributesSelect()
| SELECT attribute_id, store_id, value
| FROM catalog_product_entity_varchar
| WHERE entity_id = ? AND store_id IN (0, ?)
|
[6] Apply store scope fallback
| // Store value overrides default (store_id=0) if exists
|
[7] Run Frontend Model::getValue() for formatting
|
[8] Return hydrated entity object
N+1 Query Warning

Loading multiple entities individually causes N+1 queries. Use collections with addAttributeToSelect('*') or specific attributes to batch-load values.

Critical Flow

2. Entity Save with EAV Values

Saving an EAV entity writes to multiple tables: the entity table and all relevant value tables based on which attributes have been modified.

[1] Repository::save($entity)
|
[2] AbstractEntity::save($object)
|
[3] Run Backend Model::validate() for each attribute
| // Throws exception if validation fails
|
[4] Run Backend Model::beforeSave() for each attribute
| // Transform values (e.g., serialize arrays)
|
[5] Save static attributes to entity table
| INSERT/UPDATE catalog_product_entity
|
[6] For each modified EAV attribute:
|
+-->
Determine value table from backend_type
|
+-->
If value is null: DELETE from value table
|
+-->
If value exists: INSERT ... ON DUPLICATE KEY UPDATE
|
[7] Run Backend Model::afterSave() for each attribute
|
[8] Dispatch model_save_after event

Store Scope Handling

// Saving a product with store-scoped attribute $product->setStoreId(1); // French store $product->setName('Produit Exemple'); $productRepository->save($product); // This creates TWO rows in catalog_product_entity_varchar: // 1. entity_id=X, attribute_id=Y, store_id=0, value='Default Name' // 2. entity_id=X, attribute_id=Y, store_id=1, value='Produit Exemple' // To use default value (delete store-specific): $product->setName(false); // or $product->setData('name', false)
Important Flow

3. Attribute Configuration Loading

The Eav\Model\Config class loads and caches all entity types and their attributes. This happens once and is cached for subsequent requests.

  1. 1
    Eav\Model\Config::getEntityType($entityType)
    Check cache for entity type configuration
  2. 2
    Load from eav_entity_type table
    Get entity model, attribute model, table names
  3. 3
    Eav\Model\Config::getAttributes($entityType)
    Load all attributes for entity type from cache or database
  4. 4
    Join eav_attribute + additional_attribute_table
    E.g., eav_attribute + catalog_eav_attribute for products
  5. 5
    Cache with tag 'eav'
    Stored in configured cache backend (Redis, file, etc.)
Cache Invalidation

After adding/modifying attributes, flush the EAV cache: bin/magento cache:flush eav

Important Flow

4. Attribute Option Resolution

For select/multiselect attributes, option values are stored as IDs. The source model resolves these IDs to human-readable labels.

[1] Entity loaded with color = 45 (option_id)
|
[2] Template calls $product->getAttributeText('color')
|
[3] Get attribute by code from Eav\Model\Config
|
[4] Get Source Model from attribute (e.g., Table source)
|
[5] Source\Table::getOptionText(45)
|
+-->
Query eav_attribute_option_value
| WHERE option_id = 45 AND store_id IN (0, current_store)
|
[6] Return label: "Blue" (or store-specific translation)
// Getting option text in code $colorLabel = $product->getAttributeText('color'); // "Blue" // Getting all options for an attribute $attribute = $eavConfig->getAttribute(Product::ENTITY, 'color'); $options = $attribute->getSource()->getAllOptions(); // Returns: [['value' => 45, 'label' => 'Blue'], ...] // Custom source model implementation class CustomSource extends AbstractSource { public function getAllOptions() { return [ ['value' => '', 'label' => __('-- Select --')], ['value' => 'option1', 'label' => __('Option 1')], ['value' => 'option2', 'label' => __('Option 2')], ]; } }
Standard Flow

5. Attribute Set Assignment

When creating a new entity, it must be assigned an attribute set. This determines which attributes are available and how they're organized in the admin form.

[1] Admin creates new product
|
[2] Select attribute set (e.g., "Clothing")
|
[3] Load attribute set: AttributeSetRepository::get($setId)
|
[4] Load groups in set: eav_attribute_group
| WHERE attribute_set_id = ? ORDER BY sort_order
|
[5] Load attributes per group: eav_entity_attribute
| WHERE attribute_group_id = ? ORDER BY sort_order
|
[6] Render form tabs (groups) with fields (attributes)
|
[7] On save: set attribute_set_id on entity
Standard Flow

6. Backend Model Validation

Backend models provide hooks for validation and data transformation during save operations.

// Example: ArrayBackend for multiselect attributes class ArrayBackend extends AbstractBackend { public function beforeSave($object) { $attributeCode = $this->getAttribute()->getAttributeCode(); $data = $object->getData($attributeCode); // Convert array to comma-separated string for storage if (is_array($data)) { $object->setData($attributeCode, implode(',', array_filter($data))); } return $this; } public function afterLoad($object) { $attributeCode = $this->getAttribute()->getAttributeCode(); $data = $object->getData($attributeCode); // Convert back to array on load if ($data && !is_array($data)) { $object->setData($attributeCode, explode(',', $data)); } return $this; } } // Validation example public function validate($object) { $value = $object->getData($this->getAttribute()->getAttributeCode()); if ($this->getAttribute()->getIsRequired() && empty($value)) { throw new LocalizedException( __('"%1" is required.', $this->getAttribute()->getFrontendLabel()) ); } return true; }