Attribut-Sets strategisch planen statt organisch wuchern lassen
AI generated
M2
di.xml
Magento 2 · EAV · Attribute Sets · Governance
Attribut-Sets strategisch planen
statt organisch wuchern lassen

In den meisten Agenturprojekten wachsen Attribut-Sets unkontrolliert: Jeder neue Produkttyp bekommt sein eigenes Set, Namenskonventionen fehlen, und nach zwei Jahren verwaltet niemand mehr, welche der achtzig Sets tatsächlich noch produktiv genutzt werden. Wer Attribut-Sets stattdessen vor der Erstellung an Produkttypen und Geschäftsanforderungen ausrichtet, sie programmatisch über Data Patches erzeugt und über einen klaren Governance-Prozess kontrolliert, hält die EAV-Struktur wartbar, performant und für neue Entwickler nachvollziehbar.

18 Min. Lesezeit EavSetup · Data Patch · Attribute Set Repository · Console Command Magento 2.4.8 · PHP 8.4

1. Wildwuchs: Wie Attribut-Sets in Agenturprojekten unkontrolliert wachsen

In fast jedem gewachsenen Magento-Projekt findet sich dasselbe Bild: eine dreistellige Zahl an Attribut-Sets, von denen niemand mehr genau erklären kann, wofür die Hälfte gedacht war. Der Grund ist selten technisch, sondern organisatorisch. Ein neuer Produkttyp kommt hinzu, ein Merchandiser klickt im Admin auf "Neues Set anlegen", vergibt einen Namen wie "Set 2" oder "Test Winterkollektion" und speichert. Niemand prüft, ob ein bestehendes Set mit einer zusätzlichen Attribut-Gruppe ausgereicht hätte. Nach einem Jahr Agenturbetrieb mit mehreren Redakteuren, Praktikanten und externen Dienstleistern kommen so leicht fünfzig bis hundert Attribut-Sets zusammen, von denen ein Großteil nie wieder verwendet wird.

Das eigentliche Problem ist nicht die Anzahl an sich, sondern der fehlende Plan dahinter. Jedes Attribut-Set in Magento 2 ist eine Kombination aus Entity-Type, Attribut-Gruppen und einer Liste zugeordneter Attribute mit individueller Sortierung. Ohne eine bewusste Struktur bildet sich diese Kombination zufällig, abhängig davon, wer gerade Zugriff auf den Admin-Bereich hatte. Das Ergebnis: doppelte Sets mit fast identischem Inhalt, Sets ohne ein einziges zugeordnetes Produkt und Sets, deren Name nichts über den Zweck verrät. Jede dieser Situationen kostet später Zeit, sei es beim Onboarding neuer Entwickler, beim Produktimport oder bei der Fehlersuche, warum ein Attribut im Frontend nicht angezeigt wird.

2. Attribut-Sets strategisch planen: Produkttypen und Anforderungen zuerst

Ein tragfähiger Ansatz beginnt vor dem ersten Klick im Admin: Welche Produkttypen gibt es im Katalog wirklich, und welche davon unterscheiden sich fachlich so stark, dass sie eigene Attribute brauchen? Ein Bekleidungshändler mit einfachen und konfigurierbaren Produkten braucht typischerweise deutlich weniger eigenständige Attribut-Sets als vermutet, wenn man Varianten über Attribut-Gruppen statt über komplett neue Sets abbildet. Die Faustregel: ein neues Set lohnt sich, wenn sich die Menge der relevanten Attribute signifikant unterscheidet, nicht wenn nur ein einzelnes Zusatzattribut fehlt. Für Letzteres reicht es, das Attribut dem bestehenden Set hinzuzufügen.

In der Praxis hat sich ein kurzer Planungsschritt vor jedem neuen Modul bewährt: eine Tabelle mit Produkttyp, den fachlich zwingenden Attributen und der Frage, ob ein bestehendes Set diese Attribute bereits vollständig oder fast vollständig abdeckt. Erst wenn diese Prüfung negativ ausfällt, folgt die Erstellung eines neuen Attribut-Sets. Diese Denkweise verhindert die häufigste Wurzelursache des Wildwuchses: die Annahme, dass jede neue Produktkategorie automatisch ein eigenes Set braucht, obwohl Kategorie und Attribut-Set in Magento 2 vollkommen unabhängige Konzepte sind. Kategorien steuern die Navigation, Attribut-Sets steuern, welche Felder ein Produkt im Admin-Formular und potenziell im Frontend zeigt.

3. Attribut-Gruppen und sort_order innerhalb eines Sets

Innerhalb eines Attribut-Sets sorgen Attribut-Gruppen für die Struktur, die im Produkt-Editor als Tabs oder Abschnitte sichtbar wird. Jede Gruppe hat einen eigenen sort_order, der die Reihenfolge der Tabs bestimmt, und jedes Attribut innerhalb einer Gruppe hat ebenfalls einen eigenen sort_order, der die Reihenfolge der Felder innerhalb des Tabs festlegt. Diese doppelte Sortierung wird in der Praxis oft übersehen, weil der Standard-Import und die Standard-Attribute mit sinnvollen Werten vorbelegt sind. Sobald aber eigene Gruppen hinzukommen, etwa "Technische Daten" oder "SEO-Attribute", muss die Sortierung bewusst gesetzt werden, sonst landen neue Felder ans Ende der Liste und werden von Redakteuren übersehen.

Sinnvoll strukturierte Gruppen reduzieren zudem den Bedarf an komplett neuen Attribut-Sets: Statt für jeden Produkttyp ein eigenes Set mit denselben Basisattributen anzulegen, kann eine zusätzliche, optionale Gruppe im bestehenden Set die abweichenden Felder aufnehmen. Das folgende Beispiel zeigt, wie eine neue Gruppe samt Sortierung über EavSetup in einer Data Patch angelegt wird, inklusive der Zuordnung eines bestehenden Attributs mit eigenem sort_order innerhalb der Gruppe.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Setup\Patch\Data;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;

/**
 * Adds a dedicated "Technische Daten" attribute group with explicit sort_order
 * to the existing "Default" attribute set instead of creating a new set.
 */
final class AddTechnicalDataGroup implements DataPatchInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
    ) {
    }

    /**
     * Creates the group and assigns an existing attribute with its own sort_order.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

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

        $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);
        $attributeSetId = $eavSetup->getAttributeSetId($entityTypeId, 'Default');

        // Group sort_order 25 places the new tab after "Advanced Pricing"
        $groupId = $eavSetup->addAttributeGroup(
            $entityTypeId,
            $attributeSetId,
            'Technische Daten',
            25
        );

        // Attribute sort_order 10 is the first field inside the new group
        $eavSetup->addAttributeToGroup(
            $entityTypeId,
            $attributeSetId,
            $groupId,
            'material',
            10
        );

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

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

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

4. Klonen vs. Neuanlage: Die richtige Basis wählen

Wenn ein neuer Produkttyp fachlich tatsächlich ein eigenes Attribut-Set rechtfertigt, stellt sich die nächste Frage: von Grund auf neu anlegen oder ein bestehendes Set klonen? Magento 2 bietet dafür im Admin-Formular unter "Neues Attribut-Set" das Feld "Basierend auf", das intern die Methode initFromSkeleton() der Klasse Magento\Eav\Model\Entity\Attribute\Set nutzt. Diese Methode kopiert sämtliche Attribut-Gruppen samt ihrer Sortierung sowie alle zugeordneten Attribute des gewählten Skeleton-Sets in das neue Set. Das ist in den allermeisten Fällen die richtige Wahl, weil ein neuer Produkttyp selten komplett andere Basisattribute braucht als der Rest des Katalogs, etwa Name, Beschreibung, Preis oder SEO-Felder.

Neuanlage von Grund auf ist nur dann sinnvoll, wenn ein Set bewusst minimal gehalten werden soll, etwa für einen sehr schlanken Import von Ersatzteil- oder Zubehördaten ohne die üblichen Marketing-Attribute. In der Praxis führt das Klonen zu deutlich konsistenteren Attribut-Sets, weil Basisstruktur und Sortierung über alle Sets hinweg gleich bleiben und nur die abweichenden Attribute ergänzt werden müssen. Das folgende Beispiel klont ein bestehendes Set programmatisch, was sich anbietet, wenn dieser Schritt reproduzierbar über mehrere Umgebungen laufen soll, statt einmalig im Admin ausgeführt zu werden.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Setup\Patch\Data;

use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Eav\Api\Data\AttributeSetInterfaceFactory;
use Magento\Eav\Model\Entity\Attribute\Set;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;

/**
 * Clones the "Default" attribute set into a new "Konfigurierbar - Bekleidung" set
 * using initFromSkeleton so groups, sort_order and attributes stay consistent.
 */
final class CloneClothingAttributeSet implements DataPatchInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
        private readonly AttributeSetRepositoryInterface $attributeSetRepository,
        private readonly AttributeSetInterfaceFactory $attributeSetFactory,
    ) {
    }

    /**
     * Clones the skeleton set and persists the new set via the repository.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $entityTypeId = (int) $eavSetup->getEntityTypeId(Product::ENTITY);
        $skeletonId = (int) $eavSetup->getAttributeSetId($entityTypeId, 'Default');

        /** @var Set $newSet */
        $newSet = $this->attributeSetFactory->create();
        $newSet->setEntityTypeId($entityTypeId);
        $newSet->setAttributeSetName('Konfigurierbar - Bekleidung');

        // Copies groups, sort_order and attribute assignments from the skeleton set
        $newSet->validate();
        $newSet->initFromSkeleton($skeletonId);
        $this->attributeSetRepository->save($newSet);

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

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

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

5. Programmatische Erstellung mit Data Patch und EavSetup

Sobald feststeht, dass ein neues Attribut-Set tatsächlich von Grund auf entstehen soll, gehört diese Erstellung in eine versionierte Data Patch, nicht in einen manuellen Admin-Klick. Der Unterschied ist entscheidend für Reproduzierbarkeit: Eine Data Patch läuft bei jedem setup:upgrade in jeder Umgebung exakt einmal, ist Teil des Code-Reviews und lässt sich über die patch_list-Tabelle nachvollziehen. Ein im Admin manuell angelegtes Set existiert dagegen nur in der Datenbank, in der es erstellt wurde, was bei Staging, Produktion und lokalen Entwicklungsumgebungen zu abweichenden Ständen führt, wenn niemand die manuelle Anlage dokumentiert.

Magento stellt für diese Aufgabe die Klasse Magento\Eav\Setup\EavSetup bereit, die über eine EavSetupFactory injiziert wird. Die Methode addAttributeSet() erzeugt das Set, addAttributeGroup() die zugehörigen Gruppen und addAttributeToGroup() ordnet vorhandene Attribute den Gruppen zu. Wichtig dabei: addAttributeSet() erzeugt intern bereits eine Standardgruppe, sodass eigene Aufrufe von addAttributeGroup() nur für zusätzliche Gruppen nötig sind. Die folgende Data Patch zeigt die vollständige Erstellung eines neuen Attribut-Sets für digitale Produkte, inklusive einer eigenen Gruppe und der Zuordnung mehrerer Attribute mit expliziter Sortierung.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Setup\Patch\Data;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;

/**
 * Creates a fresh "Digital - Lizenzprodukt" attribute set from scratch
 * with its own group and explicit attribute sort_order.
 */
final class CreateDigitalProductAttributeSet implements DataPatchInterface
{
    /**
     * @param ModuleDataSetupInterface $moduleDataSetup Setup connection wrapper
     * @param EavSetupFactory $eavSetupFactory Factory for the EAV setup helper
     */
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
    ) {
    }

    /**
     * Creates the attribute set, a dedicated group and assigns attributes.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);

        // Cloning the "Default" set as skeleton keeps base attributes consistent
        $skeletonId = $eavSetup->getAttributeSetId($entityTypeId, 'Default');
        $attributeSetId = $eavSetup->addAttributeSet(
            $entityTypeId,
            'Digital - Lizenzprodukt',
            null,
            $skeletonId
        );

        $groupId = $eavSetup->addAttributeGroup(
            $entityTypeId,
            $attributeSetId,
            'Lizenzdaten',
            30
        );

        $eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'license_type', 10);
        $eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'license_duration', 20);
        $eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'download_limit', 30);

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

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

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

6. Attribut-Sets zuweisen: programmatisch und per Import

Ein neues Attribut-Set nützt wenig, solange keine Produkte darauf verweisen. Programmatisch geschieht die Zuweisung über ProductRepositoryInterface: Ein Produkt wird geladen, setAttributeSetId() gesetzt und über save() persistiert. Wichtig ist dabei, dass ein Wechsel des Attribut-Sets auf einem bestehenden Produkt Attributwerte nicht automatisch löscht, sie werden lediglich im neuen Set nicht mehr angezeigt, solange das Attribut dort nicht zugeordnet ist. Für Massenänderungen an vielen Produkten empfiehlt sich statt einer Schleife über save() die Nutzung der Produkt-Massenaktionen im Admin-Grid oder ein eigener Batch-Prozess mit expliziter Fehlerbehandlung pro Produkt, damit ein einzelner ungültiger Datensatz nicht den gesamten Lauf abbricht.

Beim CSV-Import ist die Zuordnung über die Spalte attribute_set_code vorgesehen, die den Namen des Attribut-Sets als Text erwartet, nicht die numerische ID. Der Import-Mechanismus in Magento\CatalogImportExport\Model\Import\Product validiert diesen Wert gegen die Liste existierender Sets und bricht die betroffene Zeile mit einem Validierungsfehler ab, wenn der Name nicht exakt existiert. Genau hier zeigt sich, warum konsistente Namensgebung kein kosmetisches Detail ist: Tippfehler oder mehrere Sets mit fast identischem Namen wie "Bekleidung" und "Bekleidung " mit Leerzeichen führen zu stillen Fehlzuordnungen oder abgebrochenen Importzeilen, deren Ursache im Fehlerprotokoll nicht sofort ersichtlich ist.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Console;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Eav\Api\Data\AttributeSetSearchResultsInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Reassigns a batch of products to a target attribute set by SKU list,
 * failing individually per SKU instead of aborting the whole run.
 */
final class ReassignAttributeSetCommand extends Command
{
    public function __construct(
        private readonly ProductRepositoryInterface $productRepository,
        private readonly AttributeSetRepositoryInterface $attributeSetRepository,
        private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
    ) {
        parent::__construct('mironsoft:attribute-set:reassign');
    }

    /**
     * Configures the command name, arguments and description.
     *
     * @return void
     */
    protected function configure(): void
    {
        $this->setDescription('Reassigns products to a target attribute set by SKU');
        $this->addArgument('attribute-set-name', InputArgument::REQUIRED);
        $this->addArgument('skus', InputArgument::IS_ARRAY | InputArgument::REQUIRED);
    }

    /**
     * @param InputInterface $input Command input
     * @param OutputInterface $output Command output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $setName = (string) $input->getArgument('attribute-set-name');
        $criteria = $this->searchCriteriaBuilder
            ->addFilter('attribute_set_name', $setName)
            ->create();

        /** @var AttributeSetSearchResultsInterface $result */
        $result = $this->attributeSetRepository->getList($criteria);
        $items = $result->getItems();
        if (count($items) === 0) {
            $output->writeln(sprintf('<error>Attribute set "%s" not found</error>', $setName));
            return Command::FAILURE;
        }

        $targetSetId = current($items)->getAttributeSetId();

        foreach ((array) $input->getArgument('skus') as $sku) {
            try {
                $product = $this->productRepository->get((string) $sku);
                $product->setAttributeSetId((int) $targetSetId);
                $this->productRepository->save($product);
                $output->writeln(sprintf('[OK] %s moved to set %s', $sku, $setName));
            } catch (\Throwable $exception) {
                $output->writeln(sprintf('[FAIL] %s: %s', $sku, $exception->getMessage()));
            }
        }

        return Command::SUCCESS;
    }
}

7. Governance-Prozess: Berechtigungen und Namenskonventionen

Technische Lösungen wie Data Patches verhindern Wildwuchs nur, wenn sie auch tatsächlich genutzt werden. Der zweite, mindestens ebenso wichtige Baustein ist ein Governance-Prozess: Wer im Team darf ein neues Attribut-Set überhaupt anlegen? In den meisten Agenturprojekten ist die Antwort implizit "jeder mit Admin-Zugriff", was in der Praxis zu genau dem Wildwuchs führt, der eingangs beschrieben wurde. Ein wirksamer Ansatz beschränkt das native ACL-Recht für die Attribut-Set-Verwaltung im Admin über die Benutzerrollen-Verwaltung auf einen kleinen Kreis, typischerweise die Backend-Entwicklung und einen benannten Verantwortlichen auf Kundenseite, während Redakteure und Merchandiser weiterhin Produkte bearbeiten, aber keine neuen Sets erzeugen können.

Ergänzend dazu gehört eine schriftlich festgehaltene Namenskonvention zum Pflichtprogramm, etwa nach dem Schema Produkttyp, Kategorie, optionale Variante, also "Konfigurierbar - Bekleidung" oder "Einfach - Elektronik - Zubehör". Ein solches Schema macht den Zweck eines Attribut-Sets sofort erkennbar, ohne dass jemand die zugeordneten Attribute einzeln durchsehen muss, und verhindert die im Import beschriebenen Kollisionen durch fast identische Namen. In Projekten mit mehreren Beteiligten hat es sich bewährt, diese Konvention nicht nur zu dokumentieren, sondern auch technisch über ein Observer- oder Plugin-Pattern auf dem Speichervorgang des Attribut-Set-Controllers zu erzwingen: Ein Name, der nicht dem definierten Muster entspricht, wird mit einer verständlichen Fehlermeldung abgelehnt, statt stillschweigend gespeichert zu werden. So bleibt die Konvention auch dann wirksam, wenn die schriftliche Dokumentation in Vergessenheit gerät.

8. Performance- und Indexierungs-Auswirkungen

Eine häufig unterschätzte Folge zu vieler Attribut-Sets betrifft die Admin-Performance, nicht primär das Frontend. Das Dropdown zur Auswahl des Attribut-Sets auf der "Neues Produkt"-Seite rendert bei mehreren hundert Einträgen spürbar langsamer, weil die Liste ungefiltert und unsortiert als HTML-Select aufgebaut wird. Gravierender ist der Effekt auf die interne Konfigurations-Auflösung: Magento\Eav\Model\Config::getEntityAttributes() wird pro Kombination aus Entity-Type und Attribut-Set aufgerufen und das Ergebnis separat gecacht. Je mehr unterschiedliche Sets existieren, desto mehr einzelne Cache-Einträge müssen nach einer Cache-Leerung neu aufgebaut werden, was sich beim ersten Laden jeder Produktseite im Admin nach einem Deploy als spürbare Verzögerung bemerkbar macht.

Für die Katalog-Indexer selbst ist die reine Anzahl an Attribut-Sets ein sekundärer Faktor, da die relevanten Indexer wie "Product EAV" primär attributbezogen und nicht set-bezogen arbeiten. Der eigentliche Performance-Hebel liegt in der Konsequenz aus unkontrolliertem Wachstum: Wenn viele Sets nahezu identische Attribute in leicht unterschiedlicher Kombination enthalten, wächst die Zahl der eindeutigen Attribut-Konfigurationen, die das System vorhalten und cachen muss, ohne dass ein fachlicher Mehrwert entsteht. Für Importe gilt ähnliches: Eine Validierung gegen mehrere hundert Attribut-Sets pro Zeile dauert zwar dank indexierter Namenssuche nicht merklich länger, aber die Fehleranfälligkeit durch Tippfehler oder Verwechslungen steigt proportional zur Anzahl ähnlich benannter Sets.

9. Sicher konsolidieren: ungenutzte Attribut-Sets im Vergleich

Bevor ein Attribut-Set gelöscht wird, muss zweifelsfrei feststehen, dass kein Produkt mehr darauf verweist. Eine einfache Prüfabfrage gruppiert die Tabelle catalog_product_entity nach attribute_set_id und vergleicht das Ergebnis mit der Liste aller Sets aus eav_attribute_set. Sets, die in dieser Gegenüberstellung mit null zugeordneten Produkten auftauchen, sind Kandidaten für die Bereinigung, sollten aber vor dem Löschen zusätzlich gegen Entwurfsprodukte, deaktivierte Produkte und Multi-Store-Zuordnungen geprüft werden, da ein Produkt trotz "Disabled"-Status weiterhin ein Attribut-Set referenziert. Erst nach dieser doppelten Prüfung sollte die Löschung über AttributeSetRepositoryInterface::deleteById() erfolgen, da diese Methode die referenzielle Integrität der zugehörigen Gruppen und Attribut-Zuordnungen korrekt auflöst, während ein direkter Eingriff in die Datenbank verwaiste Zeilen in eav_attribute_group und eav_entity_attribute hinterlassen kann.

Für ein produktives Audit-Werkzeug lohnt sich ein eigenes Console Command, das genau diese Gegenüberstellung automatisiert ausführt und tabellarisch ausgibt, ergänzt um eine System.xml-Konfiguration, über die ein Schwellenwert für die Mindestanzahl an Produkten pro Attribut-Set hinterlegt werden kann. So lässt sich der Audit-Lauf regelmäßig per Cron ausführen und meldet automatisch Sets, die unterhalb des konfigurierten Schwellenwerts liegen, statt dass jemand die Prüfung manuell und unregelmäßig anstößt. Der Schwellenwert selbst wird über ScopeConfigInterface aus einem eigenen system.xml-Feld gelesen, sodass Kunde oder Projektleitung ihn ohne Code-Änderung anpassen können.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Console;

use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;

/**
 * Audits all attribute sets and reports those below the configured
 * minimum product threshold, read from system.xml via ScopeConfigInterface.
 */
final class AuditAttributeSetsCommand extends Command
{
    private const XML_PATH_MIN_PRODUCTS = 'mironsoft_attributesetguard/general/min_products_threshold';

    public function __construct(
        private readonly AttributeSetRepositoryInterface $attributeSetRepository,
        private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
        private readonly ResourceConnection $resourceConnection,
        private readonly ScopeConfigInterface $scopeConfig,
    ) {
        parent::__construct('mironsoft:attribute-set:audit');
    }

    /**
     * Configures the command name and description.
     *
     * @return void
     */
    protected function configure(): void
    {
        $this->setDescription('Lists attribute sets below the configured minimum product count');
    }

    /**
     * Compares eav_attribute_set against catalog_product_entity and prints
     * every set whose product count is below the configured threshold.
     *
     * @param InputInterface $input Command input
     * @param OutputInterface $output Command output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $threshold = (int) $this->scopeConfig->getValue(self::XML_PATH_MIN_PRODUCTS);
        $connection = $this->resourceConnection->getConnection();

        $productCounts = $connection->fetchPairs(
            $connection->select()
                ->from($this->resourceConnection->getTableName('catalog_product_entity'), ['attribute_set_id'])
                ->columns(['product_count' => 'COUNT(*)'])
                ->group('attribute_set_id')
        );

        $criteria = $this->searchCriteriaBuilder->create();
        $rows = [];
        foreach ($this->attributeSetRepository->getList($criteria)->getItems() as $attributeSet) {
            $setId = (int) $attributeSet->getAttributeSetId();
            $count = (int) ($productCounts[$setId] ?? 0);
            if ($count < $threshold) {
                $rows[] = [$setId, $attributeSet->getAttributeSetName(), $count];
            }
        }

        $table = new Table($output);
        $table->setHeaders(['Set ID', 'Name', 'Products'])->setRows($rows);
        $table->render();

        return Command::SUCCESS;
    }
}
Aufgabe Unsicher / Riskant Empfohlenes Muster Vorteil
Set löschen DELETE FROM eav_attribute_set AttributeSetRepositoryInterface::deleteById() Referenzielle Integrität bleibt erhalten, keine verwaisten Zeilen
Neues Set anlegen Admin-UI ad hoc, undokumentiert Data Patch mit EavSetup, versioniert Reproduzierbar in jeder Umgebung, Code-Review möglich
Namensgebung "Set 2", "Test", "NEU final" Produkttyp - Kategorie - Variante Zweck sofort erkennbar, keine Kollisionen im Import
Set klonen Attribute manuell nachbauen initFromSkeleton() auf Basis-Set Gruppen und sort_order werden korrekt übernommen
Zuweisung im Import attribute_set_id fest verdrahtet attribute_set_code, vorab validiert Import bricht kontrolliert ab statt falsches Set zuzuweisen

Der Wert dieser Tabelle liegt nicht in Einzeltipps, sondern in der Konsequenz über das gesamte Projekt hinweg. Ein Team, das für die Hälfte der Vorgänge das empfohlene Muster nutzt und bei der anderen Hälfte auf den ad-hoc-Weg zurückfällt, hat am Ende trotzdem denselben Wildwuchs, nur langsamer. Erst die durchgängige Anwendung, unterstützt durch ein Audit-Werkzeug, das Abweichungen sichtbar macht, hält die Anzahl der Attribut-Sets dauerhaft im Rahmen der tatsächlichen fachlichen Anforderungen.

Mironsoft

Magento 2 Architektur, EAV-Governance und Hyvä-Frontend

Attribut-Sets außer Kontrolle geraten?

Wir analysieren bestehende Attribut-Sets, konsolidieren ungenutzte Strukturen und bauen Data Patches und Audit-Werkzeuge, die Wildwuchs in eurem Katalog dauerhaft verhindern.

Bestandsaudit

Analyse aller Attribut-Sets, Identifikation ungenutzter und doppelter Strukturen

Migration & Konsolidierung

Sichere Zusammenführung über Data Patches statt riskanter manueller Eingriffe

Governance-Setup

ACL-Rechte, Namenskonventionen und Audit-Commands für nachhaltige Kontrolle

10. Zusammenfassung

Attribut-Sets sind kein technisches Randthema, sondern ein zentraler Baustein der Katalogarchitektur, der ohne Planung schnell aus dem Ruder läuft. Der Schlüssel liegt darin, Attribut-Sets an tatsächlichen Unterschieden bei Produkttypen auszurichten, statt sie für jede kleine Abweichung neu zu erzeugen. Attribut-Gruppen mit sauber gesetztem sort_order lösen die meisten Anforderungen, für die reflexhaft ein neues Set angelegt würde. Wo ein neues Set wirklich nötig ist, gehört die Erstellung in eine versionierte Data Patch mit EavSetup, idealerweise auf Basis eines geklonten Skeleton-Sets über initFromSkeleton.

Genauso wichtig wie die Technik ist der organisatorische Rahmen: klare Berechtigungen, wer neue Attribut-Sets anlegen darf, eine verbindliche Namenskonvention und ein Audit-Werkzeug, das regelmäßig ungenutzte Sets sichtbar macht, bevor sie sich unbemerkt ansammeln. Wer diese beiden Ebenen, technische Erzeugung und organisatorische Kontrolle, konsequent zusammen denkt, verhindert genau den Wildwuchs, der in ungeplanten Agenturprojekten nach wenigen Jahren zur Regel wird.

Attribut-Sets in Magento 2, das Wichtigste auf einen Blick

Planung vor Erstellung

Neues Set nur bei fachlich signifikant abweichenden Attributen, sonst reicht eine zusätzliche Attribut-Gruppe im bestehenden Set.

Programmatische Erstellung

Data Patch mit EavSetup und initFromSkeleton statt Admin-Ad-hoc-Klick. Versioniert, reproduzierbar, code-reviewbar.

Governance & Namenskonvention

ACL-Recht auf wenige Rollen beschränken. Feste Namenskonvention wie Produkttyp - Kategorie - Variante durchsetzen.

Performance & Konsolidierung

Regelmäßiges Audit gegen catalog_product_entity, Löschung ausschließlich über AttributeSetRepositoryInterface.

11. FAQ: Attribut-Sets in Magento 2 verwalten

1Was ist ein Attribut-Set in Magento 2 genau?
Eine Kombination aus Attribut-Gruppen und zugeordneten Attributen für einen Entity-Type wie das Produkt. Es bestimmt, welche Felder im Admin-Formular sichtbar sind.
2Warum wuchern Attribut-Sets in Agenturprojekten?
Weil jeder Admin-Nutzer eigene Sets anlegen kann, ohne zu prüfen, ob ein bestehendes Set ausgereicht hätte. Ohne Governance sammeln sich über Jahre redundante Sets an.
3Wie plane ich Attribut-Sets sinnvoll?
Erst prüfen, ob ein bestehendes Set die Anforderungen abdeckt. Neues Set nur bei signifikant abweichenden Attributen, nicht bei einem einzelnen fehlenden Feld.
4Unterschied Attribut-Gruppen und Attribut-Sets?
Ein Set bündelt mehrere Gruppen. Die Gruppe ist der Tab im Produkt-Editor, das Set die übergeordnete Zuordnung, die ein Produkt referenziert.
5Wann klonen statt neu erstellen?
Fast immer. initFromSkeleton übernimmt Basisattribute, Gruppen und sort_order konsistent. Neuanlage nur für bewusst minimale Sets.
6Wie erstelle ich Sets per Data Patch?
DataPatchInterface implementieren, EavSetupFactory per Konstruktor injizieren, in apply() addAttributeSet, addAttributeGroup und addAttributeToGroup aufrufen.
7Zuweisung per Import?
Über die Spalte attribute_set_code mit exaktem Namen als Text. Der Import validiert gegen existierende Sets und bricht bei Abweichungen die Zeile ab.
8Wer darf neue Sets anlegen?
Ein kleiner, klar benannter Kreis, typischerweise Backend-Entwicklung. Das ACL-Recht dafür über die Benutzerrollen einschränken.
9Wirken sich viele Sets auf Performance aus?
Vor allem auf die Admin-Performance durch mehr Cache-Einträge pro Set-Kombination. Die Indexer selbst arbeiten primär attributbezogen.
10Ungenutztes Set sicher entfernen?
Erst per Abfrage gegen catalog_product_entity prüfen, dann ausschließlich über AttributeSetRepositoryInterface::deleteById() löschen, nie per direktem DELETE.

Mironsoft

Magento 2 Architektur, EAV-Governance und Hyvä-Frontend

Ordnung in euren Attribut-Sets schaffen?

Von der Bestandsaufnahme über Data Patches bis zum Audit-Werkzeug: Wir bringen Struktur in gewachsene Magento-Kataloge und verhindern, dass Attribut-Sets erneut unkontrolliert wachsen.

Analyse

Vollständige Bestandsaufnahme aller Attribut-Sets und ihrer tatsächlichen Nutzung

Umsetzung

Data Patches für Konsolidierung, Zuweisung und Bereinigung ohne Datenverlust

Langfristig

Governance-Prozess und Audit-Command für dauerhaft saubere Strukturen