Writing the Model and Resource Model for an EAV Entity
Writing the Model and Resource Model for an EAV Entity
~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
The tables from chapter 11 exist, but no PHP code knows about them yet. Unlike the flat points ledger (chapter 4, AbstractDb), an EAV entity needs a resource model that can juggle several value tables at once - Magento's Magento\Eav\Model\Entity\AbstractEntity handles exactly that work.
The model
The model itself barely differs from the flat pattern in chapter 4 - it still extends AbstractModel and binds to its resource model. New is the ENTITY constant: the entity type code that both the resource model and the setup script from chapter 13 reference, instead of repeating the string mironsoft_loyalty_reward in multiple places.
<?php
declare(strict_types=1);
namespace Mironsoft\Loyalty\Model;
use Magento\Framework\Model\AbstractModel;
use Mironsoft\Loyalty\Model\ResourceModel\Reward as RewardResource;
/**
* Reward EAV entity model, represents a single redeemable reward.
*/
class Reward extends AbstractModel
{
/**
* @var string
*/
public const ENTITY = 'mironsoft_loyalty_reward';
/**
* Binds the model to its resource model.
*
* @return void
*/
protected function _construct(): void
{
$this->_init(RewardResource::class);
}
}The resource model: AbstractEntity instead of AbstractDb
AbstractEntity takes on far more than AbstractDb: it knows the main table, automatically determines the attributes belonging to the entity type when loading, fetches their values from the matching value tables, and adds them to the model as ordinary data fields - $reward->getTitle() ends up working exactly like on a flat entity, even though the value comes from a completely different table than $reward->getIdentifier().
<?php
declare(strict_types=1);
namespace Mironsoft\Loyalty\Model\ResourceModel;
use Magento\Eav\Model\Entity\AbstractEntity;
use Magento\Framework\App\ResourceConnection;
use Mironsoft\Loyalty\Model\Reward as RewardModel;
/**
* EAV resource model for the reward entity, maps entity_id onto
* mironsoft_loyalty_reward_entity and its five attribute value tables.
*/
class Reward extends AbstractEntity
{
/**
* Sets the entity type and binds the default read/write connection.
*
* @return void
*/
protected function _construct(): void
{
$this->setType(RewardModel::ENTITY);
$this->setConnection(ResourceConnection::DEFAULT_CONNECTION);
}
/**
* Declares the columns of the main entity table that are stored directly on
* mironsoft_loyalty_reward_entity, NOT as EAV attribute values.
*
* @return array<int, string>
*/
protected function _getDefaultAttributes(): array
{
return array_merge(parent::_getDefaultAttributes(), [
'entity_id',
'attribute_set_id',
'type_id',
'identifier',
'created_at',
'updated_at',
]);
}
}What setConnection() and _getDefaultAttributes() actually do
setType()tellsAbstractEntitywhich row ineav_entity_type(chapter 13) belongs to this resource model - that's how it finds the main table, the attribute set, and all registered attributes.setConnection(ResourceConnection::DEFAULT_CONNECTION)uses the same default connection for reading and writing; large EAV entities likecatalog_productsplit this intocatalog_read/catalog_write, which isn't necessary for rewards._getDefaultAttributes()tells the base class which columns are not resolved viaeav_attribute, but come straight from the main table - without this list,AbstractEntitywould try to look up an EAV value table entry foridentifiertoo.
Achtung: A forgotten column in _getDefaultAttributes() doesn't show up when saving, only when reading: AbstractEntity then tries to load a non-existent EAV attribute entry for that column and simply returns null - a silent bug, not an error.
Why no repository yet?
Unlike the points ledger in chapter 6, this block deliberately does not yet introduce a RewardRepositoryInterface or an Api\Data\RewardInterface. That follows specifically in block 10, once the reward is actually exposed via REST and GraphQL and a service contract serves a real purpose (chapter 79 builds explicitly on top of this). Until then, direct access through the model, resource model, and - starting in chapter 15 - the collection is enough, just as many purely admin-side EAV grids in Magento itself do.
The model and resource model alone are already enough to load and save a single reward: $rewardResource->load($reward, $entityId) and $rewardResource->save($reward). Chapter 13 first makes sure that attributes exist at all that could be loaded this way.