Attribut-Sichtbarkeit und Scope richtig konfigurieren (Website/Store/Global)
Attribut-Sichtbarkeit und Scope richtig konfigurieren (Website/Store/Global)
~7 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Kapitel 19 hat loyalty_points_multiplier mit SCOPE_GLOBAL angelegt, Kapitel 20 loyalty_bonus_category direkt mit SCOPE_WEBSITE - beide Entscheidungen wurden angekündigt, aber nicht ausführlich begründet. Dieses Kapitel liefert die Begründung nach und zeigt, wie sich der Scope eines bereits existierenden Attributs nachträglich ändern lässt, ohne die Attribut-Definition selbst neu anzulegen.
Die drei Scope-Stufen
\Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface definiert drei Konstanten, die für Produkt-, Kategorie- und Kunden-Attribute gleichermaßen gelten:
SCOPE_GLOBAL(Wert0) - ein einziger Wert für die komplette Installation, unabhängig von Website oder Store View. Änderungen wirken sich sofort auf alle Stores aus.SCOPE_WEBSITE(Wert1) - ein eigener Wert pro Website; alle Store Views derselben Website teilen sich diesen Wert.SCOPE_STORE(Wert2) - ein eigener Wert pro Store View, die feinste verfügbare Granularität.
Achtung: Die Namensgebung ist bei Kundenattributen leicht irreführend: Im Admin-Formular für Kundenattribute heißt die Dropdown-Option für SCOPE_WEBSITE schlicht "Website", wird intern aber genauso über ScopedAttributeInterface abgebildet wie bei Produkten - derselbe Mechanismus, nicht zwei verschiedene.
Warum loyalty_points_multiplier Website-Scope braucht
Dieses Projekt selbst liefert das beste Beispiel: CLAUDE.md beschreibt einen Dual-Vendor-Workflow, bei dem dasselbe Modul unter zwei Markennamen (Mironsoft/Abrams) parallel betrieben wird - in der Praxis oft als zwei Websites in derselben Magento-Installation. Ein globaler Punkte-Multiplikator würde erzwingen, dass beide Marken exakt dieselbe Punkte-Großzügigkeit fahren, obwohl das Business dahinter unterschiedlich sein kann. Ein nachträglicher Data Patch ändert den Scope, ohne das Attribut zu löschen und neu anzulegen:
<?php
declare(strict_types=1);
namespace Mironsoft\Loyalty\Setup\Patch\Data;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
/**
* Changes loyalty_points_multiplier from global to website scope, so the
* Mironsoft and Abrams websites (see CLAUDE.md dual-vendor workflow) can run
* different point multipliers.
*/
class UpdateProductLoyaltyAttributeScope implements DataPatchInterface
{
/**
* @param ModuleDataSetupInterface $moduleDataSetup Provides the setup connection for the patch.
* @param EavSetupFactory $eavSetupFactory Creates the EavSetup helper used to update the attribute.
*/
public function __construct(
private readonly ModuleDataSetupInterface $moduleDataSetup,
private readonly EavSetupFactory $eavSetupFactory
) {
}
/**
* Switches the attribute's is_global property to website scope.
*
* @return void
*/
public function apply(): void
{
$this->moduleDataSetup->getConnection()->startSetup();
/** @var \Magento\Eav\Setup\EavSetup $eavSetup */
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
$eavSetup->updateAttribute(
Product::ENTITY,
'loyalty_points_multiplier',
'is_global',
ScopedAttributeInterface::SCOPE_WEBSITE
);
$this->moduleDataSetup->getConnection()->endSetup();
}
/**
* @return array<int, string>
*/
public static function getDependencies(): array
{
return [\Mironsoft\Loyalty\Setup\Patch\Data\InstallProductLoyaltyAttribute::class];
}
/**
* @return array<int, string>
*/
public function getAliases(): array
{
return [];
}
}Achtung: updateAttribute() erwartet als vierten Parameter direkt den neuen Wert der Datenbankspalte is_global, nicht ein assoziatives Array wie addAttribute(). Ein häufiger Fehler ist, ['is_global' => ...] zu übergeben - das schlägt nicht mit einer Exception fehl, sondern speichert stillschweigend einen falschen (weil gecasteten) Wert.
Scope-Änderung mit Bestandsdaten
Ein Wechsel von Global auf Website oder Store bei einem bereits produktiv genutzten Attribut ist nicht rein kosmetisch: Der bisher einzige Wert (store_id = 0) bleibt als Fallback für alle Scopes erhalten, die keinen eigenen Wert gesetzt haben - genau das store_id = 0-Verhalten, das Kapitel 18 für Rewards beschrieben hat, gilt identisch für Produkt-, Kategorie- und Kundenattribute. Erst ein tatsächlicher, gezielter Speichervorgang mit einem abweichenden Store-Kontext erzeugt eine zusätzliche Zeile.
Kein Scope-Konzept bei flachen Entitäten
loyalty_tier_override (Company, Kapitel 22) und loyalty_points_earned (Sales, Kapitel 23) kennen dagegen überhaupt keinen ScopedAttributeInterface-Mechanismus - eine flache Spalte speichert exakt einen Wert pro Zeile, unabhängig von Website oder Store. Bei Company ergibt das ohnehin Sinn (eine Firma gehört fachlich zu genau einem B2B-Kontext); bei Sales-Attributen ist die Frage gegenstandslos, weil jede Bestellung ohnehin bereits fest einem einzigen Store zugeordnet ist, sobald sie existiert. Wer für eine flache Entität dennoch "Scope-artiges" Verhalten braucht (z. B. unterschiedliche Company-Konditionen je Website), müsste das selbst modellieren - etwa über eine zusätzliche Zuordnungstabelle.
bin/magento setup:upgrade
bin/magento indexer:reindex catalog_product_attribute
bin/magento cache:flushTipp: Im Admin lässt sich der konfigurierte Scope eines Attributs jederzeit unter Stores > Attributes > Product (bzw. Category/Customer) unter "Scope" nachschlagen - ein schneller Weg, um vor einem Bugfix zu prüfen, ob ein unerwarteter Wert tatsächlich an einem falsch konfigurierten Scope liegt, statt an der eigentlichen Geschäftslogik.
Mit Scope und Sichtbarkeit geklärt, geht es in Kapitel 26 einen Schritt weiter: Backend-Modelle, die einen Attributwert nicht nur speichern, sondern beim Speichern aktiv berechnen oder validieren.