Attribut-Quellmodelle (Source Models) für Auswahllisten
Attribut-Quellmodelle (Source Models) für Auswahllisten
~7 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
LoyaltyTier ist in diesem Block bereits zweimal aufgetaucht: als source-Klasse des EAV-Attributs loyalty_tier in Kapitel 21 und als options-Klasse des Company-Formularfelds loyalty_tier_override in Kapitel 22 - obwohl customer eine EAV-Entität ist und company keine. Dieses Kapitel erklärt, warum genau dieselbe Klasse an beiden völlig unterschiedlichen Stellen funktioniert, und vertieft, wie Source Models allgemein arbeiten - über die einfache Wiederholung von Kapitel 13 hinaus.
Ein Interface, zwei Verwendungskontexte
Der gemeinsame Nenner heißt \Magento\Framework\Data\OptionSourceInterface mit seiner einzigen Methode toOptionArray(): array. \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource, von der LoyaltyTier erbt, implementiert dieses Interface bereits - toOptionArray() ruft dort intern schlicht getAllOptions() auf und formt das Ergebnis passend um. Für ein EAV-Attribut (Kapitel 21) erwartet das source-Array-Feld eine Klasse mit genau dieser Basis; für ein UI-Component-Formularfeld (Kapitel 22) genügt irgendeine Klasse, die OptionSourceInterface implementiert - AbstractSource erfüllt automatisch beide Anforderungen gleichzeitig.
interface OptionSourceInterface
{
/**
* @return array<int, array{value: string, label: string}>
*/
public function toOptionArray(): array;
}Die eingebaute Alternative: Table-Source ohne eigene Klasse
Für ein einzelnes, nirgendwo wiederverwendetes Dropdown böte sich statt einer eigenen Klasse auch die kürzere Variante über einen direkten 'option'-Schlüssel im addAttribute()-Array an - Magento verwaltet die Optionswerte dann selbst in eav_attribute_option/eav_attribute_option_value und macht sie sogar im Admin über Store > Attributes UI editierbar, ganz ohne PHP-Klasse.
$eavSetup->addAttribute(Customer::ENTITY, 'loyalty_tier', [
'type' => 'varchar',
'input' => 'select',
'option' => [
'values' => [
'bronze' => 'Bronze',
'silver' => 'Silver',
'gold' => 'Gold',
],
],
// ...
]);Achtung: Diese kürzere Variante war für loyalty_tier bewusst nicht die Wahl in Kapitel 21: Sie funktioniert ausschließlich für echte EAV-Attribute und wäre in Kapitel 22 für das Company-Formularfeld gar nicht verfügbar gewesen - eine dedizierte Source-Model-Klasse war hier die einzige Option, die an beiden Stellen wiederverwendbar ist. Für Werte, die nur an einer einzigen Stelle gebraucht werden (z. B. is_active in Kapitel 13, dort über BooleanSource gelöst statt über option), ist die Entscheidung meist reine Geschmackssache.
Caching innerhalb des Source Models
getAllOptions() von LoyaltyTier (Kapitel 21) prüft $this->_options === null, bevor sie das Array neu aufbaut - eine Konvention, die AbstractSource selbst vorgibt. Ohne diese Prüfung würde bei jedem Aufruf (potenziell mehrfach pro Seitenaufruf, etwa einmal für die Rasteranzeige und einmal für einen Formular-Rebuild) unnötig ein neues Array erzeugt - bei drei statischen Werten vernachlässigbar, bei einer über Ledger-Daten berechneten, dynamischen Optionsliste dagegen ein echter Unterschied.
addValueSortToCollection() für Grid-Filter
AbstractSource bietet zusätzlich addValueSortToCollection() an, das eine Sortierung nach dem Options-Label statt nach dem gespeicherten Rohwert ermöglicht - relevant, sobald ein Admin-Grid nach loyalty_tier sortierbar sein soll und "Bronze" alphabetisch vor "Gold" erscheinen soll, obwohl der interne Wert bronze zufällig ohnehin schon vor gold steht. Bei anders benannten internen Werten (z. B. tier_1/tier_2/tier_3) wäre der Unterschied zwischen Rohwert-Sortierung und Label-Sortierung sichtbar.
Übersetzung nicht vergessen
__('Bronze'), __('Silver'), __('Gold') in Kapitel 21 sind keine reine Stilkonvention: Ohne __()-Wrapper würden die Labels bei einer i18n-CSV-Übersetzung (Block 11, Kapitel 89-90) niemals als übersetzbare Zeichenketten erkannt und blieben im englischen/deutschen Admin-Store identisch, selbst wenn eine de_DE.csv eine Übersetzung dafür enthielte.
Tipp: Ein Source Model, das Optionen dynamisch aus der Datenbank lädt (z. B. eine Liste aktiver Prämien wie in Kapitel 13 grundsätzlich denkbar wäre), sollte niemals in seinem Konstruktor selbst laden - der Zugriff gehört strikt in getAllOptions(), damit das null-Caching aus diesem Kapitel greift und die Klasse nicht bei jeder Objekt-Instanziierung eine Datenbankabfrage auslöst, selbst wenn die Optionen am Ende gar nicht abgefragt werden.
Mit geklärter Wiederverwendbarkeit von Source Models wendet sich Kapitel 25 der zweiten großen Konfigurationsachse jedes Attributs zu: dem Scope - wer einen Wert sieht und wer ihn setzen darf, Website für Website, Store View für Store View.