Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Eigene EAV-Attribute für Prämien definieren (Setup-Skript für Attribute)

Eigene EAV-Attribute für Prämien definieren (Setup-Skript für Attribute)

~8 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026

Sechs Tabellen (Kapitel 11) und ein Model/ResourceModel-Paar (Kapitel 12) reichen noch nicht - solange eav_entity_type nichts von mironsoft_loyalty_reward weiß und eav_attribute keine der sechs Prämien-Eigenschaften kennt, gibt es für AbstractEntity schlicht nichts zu laden. Dieses Kapitel registriert beides über Data Patches - Magentos moderner, deklarativer Ersatz für die alten InstallData/UpgradeData-Skripte, im selben Geist wie CLAUDE.mds Vorgabe, db_schema.xml statt Install-Skripten für das Schema selbst zu nutzen.

Erster Patch: den Entity Type registrieren

EavSetup::addEntityType() legt die Zeile in eav_entity_type an und verknüpft sie mit ResourceModel, Attribut-Model und Haupttabelle. Ist der Entity Type neu, legt die Methode zusätzlich automatisch ein Attribut-Set namens Default mit einer Gruppe namens General an - beide reichen für Rewards vollständig aus, ein eigenes Set ist hier nicht nötig.

app/code/Mironsoft/Loyalty/Setup/Patch/Data/InstallRewardEntityType.php
<?php

declare(strict_types=1);

namespace Mironsoft\Loyalty\Setup\Patch\Data;

use Magento\Eav\Model\Entity\Attribute as EavAttribute;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection as EavAttributeCollection;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Mironsoft\Loyalty\Model\Reward;
use Mironsoft\Loyalty\Model\ResourceModel\Reward as RewardResource;

/**
 * Registers the mironsoft_loyalty_reward EAV entity type, including its
 * automatically created "Default" attribute set and "General" group.
 */
class InstallRewardEntityType implements DataPatchInterface
{
    /**
     * @param ModuleDataSetupInterface $moduleDataSetup Provides the setup connection for the patch.
     * @param EavSetupFactory $eavSetupFactory Creates the EavSetup helper used to register the entity type.
     */
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory
    ) {
    }

    /**
     * Adds the eav_entity_type row for rewards.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        /** @var \Magento\Eav\Setup\EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->addEntityType(Reward::ENTITY, [
            'entity_model' => RewardResource::class,
            'attribute_model' => EavAttribute::class,
            'table' => 'mironsoft_loyalty_reward_entity',
            'entity_attribute_collection' => EavAttributeCollection::class,
        ]);

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * @return array<int, string>
     */
    public static function getDependencies(): array
    {
        return [];
    }

    /**
     * @return array<int, string>
     */
    public function getAliases(): array
    {
        return [];
    }
}

Zweiter Patch: die sechs Attribute

Der zweite Patch hängt über getDependencies() vom ersten ab - der Entity Type muss existieren, bevor ihm Attribute zugeordnet werden können. EavSetup::addAttribute() erwartet den Entity-Type-Code, den Attribut-Code und ein Array mit type (dem backend_type, siehe Kapitel 14), input (dem Admin-Formularfeld), required und weiteren Feldern.

app/code/Mironsoft/Loyalty/Setup/Patch/Data/InstallRewardAttributes.php
<?php

declare(strict_types=1);

namespace Mironsoft\Loyalty\Setup\Patch\Data;

use Magento\Eav\Model\Entity\Attribute\Source\Boolean as BooleanSource;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Mironsoft\Loyalty\Model\Reward;
use Mironsoft\Loyalty\Model\Reward\Source\RewardType;

/**
 * Registers the six custom EAV attributes of the mironsoft_loyalty_reward entity:
 * title, description, points_cost, discount_value, reward_type, is_active.
 */
class InstallRewardAttributes implements DataPatchInterface
{
    /**
     * @param ModuleDataSetupInterface $moduleDataSetup Provides the setup connection for the patch.
     * @param EavSetupFactory $eavSetupFactory Creates the EavSetup helper used to register attributes.
     */
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory
    ) {
    }

    /**
     * Adds all six reward attributes to the "General" group of the "Default" attribute set.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        /** @var \Magento\Eav\Setup\EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);

        $eavSetup->addAttribute(Reward::ENTITY, 'title', [
            'type' => 'varchar',
            'label' => 'Title',
            'input' => 'text',
            'required' => true,
            'sort_order' => 10,
            'group' => 'General',
        ]);
        $eavSetup->addAttribute(Reward::ENTITY, 'description', [
            'type' => 'text',
            'label' => 'Description',
            'input' => 'textarea',
            'required' => false,
            'sort_order' => 20,
            'group' => 'General',
        ]);
        $eavSetup->addAttribute(Reward::ENTITY, 'points_cost', [
            'type' => 'int',
            'label' => 'Points Cost',
            'input' => 'text',
            'required' => true,
            'sort_order' => 30,
            'group' => 'General',
        ]);
        $eavSetup->addAttribute(Reward::ENTITY, 'discount_value', [
            'type' => 'decimal',
            'label' => 'Discount Value',
            'input' => 'text',
            'required' => false,
            'sort_order' => 40,
            'group' => 'General',
        ]);
        $eavSetup->addAttribute(Reward::ENTITY, 'reward_type', [
            'type' => 'varchar',
            'label' => 'Reward Type',
            'input' => 'select',
            'source' => RewardType::class,
            'required' => true,
            'sort_order' => 50,
            'group' => 'General',
        ]);
        $eavSetup->addAttribute(Reward::ENTITY, 'is_active', [
            'type' => 'int',
            'label' => 'Is Active',
            'input' => 'boolean',
            'source' => BooleanSource::class,
            'required' => false,
            'default' => '1',
            'sort_order' => 60,
            'group' => 'General',
        ]);

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * @return array<int, string>
     */
    public static function getDependencies(): array
    {
        return [InstallRewardEntityType::class];
    }

    /**
     * @return array<int, string>
     */
    public function getAliases(): array
    {
        return [];
    }
}

Das Source Model für reward_type

reward_type ist als Dropdown definiert (input => 'select') und braucht dafür ein Source Model, das die Optionsliste liefert - dieselbe Rolle, die Kapitel 24 in Block 3 für das Treue-Stufen-Dropdown am Kunden übernimmt.

app/code/Mironsoft/Loyalty/Model/Reward/Source/RewardType.php
<?php

declare(strict_types=1);

namespace Mironsoft\Loyalty\Model\Reward\Source;

use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;

/**
 * Source model for the reward_type dropdown attribute.
 */
class RewardType extends AbstractSource
{
    /**
     * @var string
     */
    public const TYPE_DISCOUNT = 'discount';

    /**
     * @var string
     */
    public const TYPE_FREE_PRODUCT = 'free_product';

    /**
     * @var string
     */
    public const TYPE_FREE_SHIPPING = 'free_shipping';

    /**
     * Returns the dropdown options shown in the admin form and used for grid filtering.
     *
     * @return array<int, array{value: string, label: string}>
     */
    public function getAllOptions(): array
    {
        if ($this->_options === null) {
            $this->_options = [
                ['value' => self::TYPE_DISCOUNT, 'label' => __('Discount')],
                ['value' => self::TYPE_FREE_PRODUCT, 'label' => __('Free Product')],
                ['value' => self::TYPE_FREE_SHIPPING, 'label' => __('Free Shipping')],
            ];
        }

        return $this->_options;
    }
}
bin/magento setup:upgrade
bin/magento cache:flush

Tipp: Data Patches laufen automatisch bei jedem setup:upgrade genau einmal - Magento merkt sich ausgeführte Patches in der Tabelle patch_list. Eine spätere Änderung an apply() wirkt sich auf bereits installierte Umgebungen nicht mehr aus; für Attribut-Änderungen nach dem ersten Release braucht es einen neuen, zusätzlichen Patch.

Achtung: getDependencies() ist eine static-Methode - ein leicht zu übersehender Unterschied zu getAliases(), das eine Instanzmethode ist. Ein PHPStan-Level-5-Lauf über Setup/Patch/Data/ macht solche Signatur-Abweichungen sofort sichtbar.

Mit registriertem Entity Type und sechs Attributen existiert jetzt eine vollständig funktionsfähige EAV-Entity. Kapitel 14 sieht sich die vier tatsächlich genutzten backend_type-Werte im Detail an.