Creating the Reward EAV Entity Tables (Entity + Attribute Tables)
Creating the Reward EAV Entity Tables (Entity + Attribute Tables)
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
With the vocabulary from chapter 10 in hand, this chapter builds what chapter 3 was for the points ledger: the tables themselves, via db_schema.xml. Six tables in total - one main table and five value tables, one per PHP scalar type.
The main table: mironsoft_loyalty_reward_entity
entity_id- primary key, auto-increment, corresponds toentity_idoncatalog_product_entity.attribute_set_id- foreign key toeav_attribute_set.attribute_set_id; chapter 13 automatically creates a "Default" set as soon as the entity type is registered.type_id- see the box below.identifier- a unique, SKU-like identifier per reward (for examplereward-10-euro-voucher), used by later blocks for URLs and API references.created_at/updated_at- timestamps maintained automatically by the database.
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
<!-- unchanged from chapter 3: mironsoft_loyalty_points_ledger -->
<table name="mironsoft_loyalty_reward_entity" resource="default" engine="innodb"
comment="Mironsoft Loyalty Reward EAV Entity">
<column xsi:type="int" name="entity_id" padding="10" unsigned="true"
nullable="false" identity="true" comment="Entity ID"/>
<column xsi:type="smallint" name="attribute_set_id" padding="5" unsigned="true"
nullable="false" identity="false" default="0" comment="Attribute Set ID"/>
<column xsi:type="varchar" name="type_id" nullable="false" length="32"
default="mironsoft_loyalty_reward" comment="Entity Type ID"/>
<column xsi:type="varchar" name="identifier" nullable="false" length="64"
comment="Unique, SKU-like Reward Identifier"/>
<column xsi:type="timestamp" name="created_at" on_update="false" nullable="false"
default="CURRENT_TIMESTAMP" comment="Created At"/>
<column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false"
default="CURRENT_TIMESTAMP" comment="Updated At"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="entity_id"/>
</constraint>
<constraint xsi:type="unique" referenceId="MIRONSOFT_LOYALTY_REWARD_ENTITY_IDENTIFIER">
<column name="identifier"/>
</constraint>
<constraint xsi:type="foreign"
referenceId="MIRONSOFT_LOYALTY_REWARD_ENTITY_ATTRIBUTE_SET_ID_EAV_ATTRIBUTE_SET_ATTRIBUTE_SET_ID"
table="mironsoft_loyalty_reward_entity" column="attribute_set_id"
referenceTable="eav_attribute_set" referenceColumn="attribute_set_id"
onDelete="CASCADE"/>
</table>
</schema>Why type_id, when there's only one type?
catalog_product_entity.type_id stores whether a product is simple, configurable, or another product type (block 9 of this series even builds a custom one). The reward has no such type variation - yet type_id is still part of the column set, because Magento's AbstractEntity base class (chapter 12) expects this column as a fixed part of the classic EAV entity pattern. For rewards it simply stays constant at mironsoft_loyalty_reward - an example of an inherited pattern not actually needing every single property of its base, but still being carried along consistently.
The attribute value tables: one per PHP scalar type
All five value tables follow the same pattern as catalog_product_entity_varchar and friends: a technical value_id primary key, a reference to the attribute (attribute_id), a reference to the store view (store_id, 0 = default/all stores), a reference to the entity (entity_id) - and the actual value. The combination of entity_id, attribute_id, and store_id is unique: there's at most one value per entity, attribute, and store view.
<table name="mironsoft_loyalty_reward_entity_varchar" resource="default" engine="innodb"
comment="Mironsoft Loyalty Reward Varchar Attribute Values">
<column xsi:type="int" name="value_id" padding="10" unsigned="true"
nullable="false" identity="true" comment="Value ID"/>
<column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true"
nullable="false" identity="false" default="0" comment="Attribute ID"/>
<column xsi:type="smallint" name="store_id" padding="5" unsigned="true"
nullable="false" identity="false" default="0" comment="Store ID"/>
<column xsi:type="int" name="entity_id" padding="10" unsigned="true"
nullable="false" identity="false" comment="Entity ID"/>
<column xsi:type="varchar" name="value" nullable="true" length="255" comment="Value"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="value_id"/>
</constraint>
<constraint xsi:type="foreign"
referenceId="MIRONSOFT_LOYALTY_REWARD_ENTITY_VARCHAR_ENTITY_ID_MIRONSOFT_LOYALTY_REWARD_ENTITY_ENTITY_ID"
table="mironsoft_loyalty_reward_entity_varchar" column="entity_id"
referenceTable="mironsoft_loyalty_reward_entity" referenceColumn="entity_id"
onDelete="CASCADE"/>
<constraint xsi:type="unique"
referenceId="MIRONSOFT_LOYALTY_REWARD_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID">
<column name="entity_id"/>
<column name="attribute_id"/>
<column name="store_id"/>
</constraint>
<index referenceId="MIRONSOFT_LOYALTY_REWARD_ENTITY_VARCHAR_ATTRIBUTE_ID" indexType="btree">
<column name="attribute_id"/>
</index>
</table>The remaining four tables (_int, _decimal, _text, _datetime) are structurally identical - the primary key, foreign key, unique constraint, and index all stay exactly the same, only referenceId and the table name swap varchar for the respective type name. The only thing that changes is the value column:
<!-- _int -->
<column xsi:type="int" name="value" nullable="true" identity="false" comment="Value"/>
<!-- _decimal -->
<column xsi:type="decimal" name="value" nullable="true" scale="4" precision="20" comment="Value"/>
<!-- _text -->
<column xsi:type="text" name="value" nullable="true" comment="Value"/>
<!-- _datetime -->
<column xsi:type="datetime" name="value" nullable="true" comment="Value"/>Achtung: Deliberately no foreign key from attribute_id to eav_attribute.attribute_id - exactly like Magento's own catalog_product_entity_* tables. The reason: eav_attribute is potentially shared by many different entity types, and overly strict referential integrity here would cost performance without a practical safety benefit - the regular attribute lifecycle runs exclusively through EAV setup code anyway, never through manual deletion.
Adding Magento_Eav to module.xml
The foreign keys to eav_attribute_set on the main table require Magento_Eav to be guaranteed to install before Mironsoft_Loyalty - exactly the same reasoning as chapter 2 for Magento_Customer and Magento_Sales.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Mironsoft_Loyalty">
<sequence>
<module name="Magento_Customer"/>
<module name="Magento_Sales"/>
<module name="Magento_Eav"/>
</sequence>
</module>
</config>bin/magento setup:upgrade
bin/magento cache:flushTipp: The referenceId convention from chapter 3 (<TABLE>_<COLUMN>_<TARGET_TABLE>_<TARGET_COLUMN>) still applies unchanged - with five nearly identical value tables it's even more important, since typos between the tables would otherwise barely stand out.
Six tables without a single line of PHP code - chapter 12 now builds the model and resource model so these tables can actually be accessed.