Magento Modul-Abhängigkeiten sauber verwalten
AI generated
M2
di.xml
Magento 2 · Modularchitektur · Composer · module.xml
Magento Modul-Abhängigkeiten sauber verwalten
Sequence, Composer und zirkuläre Abhängigkeiten auflösen

Modul-Abhängigkeiten in Magento existieren auf zwei getrennten Ebenen: composer.json steuert, welches Paket überhaupt installiert wird, und module.xml sequence steuert, in welcher Reihenfolge es geladen wird. Wer beides vermischt, baut zirkuläre Abhängigkeiten, die erst bei setup:upgrade auffallen und sich ohne sauberes Refactoring kaum mehr entwirren lassen.

15 Min. Lesezeit module.xml sequence · composer.json · Module Manager Magento 2.4.8-p4 · PHP 8.4

1. Zwei Ebenen von Abhängigkeiten in Magento

Wer Modul-Abhängigkeiten in Magento 2 verwaltet, arbeitet unweigerlich auf zwei völlig getrennten Ebenen, die häufig verwechselt werden. Die erste Ebene ist die Composer-Paketebene: composer.json entscheidet, welches Paket überhaupt via bin/composer install oder bin/composer update heruntergeladen und im vendor-Verzeichnis abgelegt wird. Die zweite Ebene ist die Magento-interne Modul-Ladereihenfolge: module.xml mit seinem <sequence>-Element entscheidet, in welcher Reihenfolge die bereits vorhandenen Module ihre etc-Konfigurationsdateien, Events und Layout-Handles zusammenführen.

Der entscheidende Fehler, der in fast jedem Projekt mit wachsender Modulanzahl auftaucht: Ein Entwickler geht davon aus, dass eine require-Angabe in composer.json automatisch auch die Ladereihenfolge in Magento regelt. Das ist falsch. Composer kümmert sich ausschließlich um die Installation der Pakete, hat aber keinerlei Bezug zur internen Magento-Modulreihenfolge. Ohne eine explizite sequence-Deklaration in module.xml lädt Magento Module in einer Reihenfolge, die zwar deterministisch, aber für den Entwickler nicht unbedingt vorhersehbar ist, weil sie unter anderem von der alphabetischen Reihenfolge und bereits vorhandenen anderen Sequence-Angaben abhängt.

Diese Verwechslung ist der Ursprung fast aller Probleme rund um Modulabhängigkeiten, von unerwarteten Ladereihenfolgen über schwer zu debuggende Events bis hin zu echten zirkulären Abhängigkeiten. In diesem Artikel klären wir beide Ebenen sauber voneinander, zeigen die korrekte Syntax für sequence, die Unterschiede zwischen require und suggest in composer.json, und ein konkretes Vorgehen, um zirkuläre Abhängigkeiten zwischen eigenen Modulen dauerhaft zu vermeiden.

2. module.xml sequence im Detail

Das <sequence>-Element in module.xml steuert ausschließlich die Reihenfolge, in der Magento die etc-Verzeichnisse der Module beim Kompilieren zusammenführt: di.xml-Merges, events.xml-Registrierungen, acl.xml-Definitionen und Layout-Handle-Reihenfolgen. Ein Modul, das in seiner sequence ein anderes referenziert, wird garantiert NACH diesem anderen Modul geladen. Das ist wichtig, wenn das eigene Modul beispielsweise ein Layout-Handle des referenzierten Moduls überschreiben oder ein Plugin auf eine dort definierte Klasse registrieren möchte, deren di.xml-Eintrag zuerst vorhanden sein muss.

Was sequence explizit NICHT steuert, ist die Composer-Installation. Ein Modul kann in seiner sequence ein anderes Modul referenzieren, ohne dass eine entsprechende Composer-Abhängigkeit existiert. In diesem Fall funktioniert setup:upgrade nur, solange das referenzierte Modul zufällig ebenfalls installiert ist. Fehlt es, meldet Magento einen expliziten Fehler beim Kompilieren, weil die sequence auf ein nicht vorhandenes Modul verweist. Deshalb gilt als feste Regel: jede sequence-Referenz sollte von einer entsprechenden require-Angabe in composer.json begleitet werden, außer es handelt sich bewusst um eine optionale, weiche Abhängigkeit.


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <!-- Vendor_PriceSync loads AFTER Magento_Catalog and Vendor_CustomerSync -->
    <module name="Vendor_PriceSync">
        <sequence>
            <module name="Magento_Catalog"/>
            <module name="Vendor_CustomerSync"/>
        </sequence>
    </module>
</config>

Ein wichtiger Zusatz: sequence erzeugt keine harte Laufzeit-Abhängigkeit im PHP-Code. Nichts hindert eine Klasse in Vendor_PriceSync daran, direkt Klassen aus Magento_Catalog zu referenzieren, unabhängig von der sequence-Deklaration. Die sequence regelt ausschließlich die Reihenfolge des XML-Merge-Vorgangs, nicht die tatsächliche Code-Kopplung zur Laufzeit. Diese Unterscheidung ist zentral für das Verständnis der nächsten Abschnitte über Modul-Abhängigkeiten auf Composer-Ebene.

3. composer.json require vs. suggest

In composer.json gibt es für Modulabhängigkeiten zwei relevante Felder: require für harte, verpflichtende Abhängigkeiten und suggest für weiche, empfohlene Abhängigkeiten. Ein require-Eintrag sorgt dafür, dass Composer das referenzierte Paket zwingend installiert, bevor das eigene Modul überhaupt funktionsfähig ist. Das ist die richtige Wahl, wenn im eigenen Code tatsächlich Klassen aus dem anderen Modul importiert und direkt genutzt werden, etwa eine Repository-Schnittstelle oder ein Service Contract.

suggest hingegen installiert nichts automatisch, sondern zeigt bei composer show lediglich einen Hinweis an, dass ein bestimmtes Paket die Funktionalität sinnvoll ergänzen würde. Der kritische Fehler, der in der Praxis häufig passiert: Ein Entwickler nutzt suggest für ein Modul, dessen Klassen im eigenen Code trotzdem direkt referenziert werden, etwa in einem optionalen Feature-Zweig. Ist das vorgeschlagene Modul nicht installiert, bricht der Autoloader beim ersten Zugriff auf die fehlende Klasse mit einem Fatal Error ab, weil PHP zur Laufzeit keine Rücksicht auf Composer-Metadaten nimmt.


{
    "name": "vendor/module-price-sync",
    "require": {
        "php": "~8.4.0",
        "magento/module-catalog": "*",
        "vendor/module-customer-sync": "*"
    },
    "suggest": {
        "vendor/module-loyalty-points": "Enables loyalty point recalculation after price sync, if installed"
    }
}

Die einzig sichere Kombination bei einer suggest-Abhängigkeit ist eine Laufzeitprüfung mit Magento\Framework\Module\Manager::isEnabled(), bevor Code aus dem vorgeschlagenen Modul überhaupt referenziert wird. Diese Absicherung wird in Abschnitt 6 im Detail behandelt. Ohne diese Prüfung ist suggest in der Praxis nicht wirklich weich, sondern nur eine unvollständig abgesicherte harte Abhängigkeit, die beim Fehlen des Pakets zur Laufzeit crasht statt sauber zu degradieren.

4. Zirkuläre Abhängigkeiten erkennen

Eine zirkuläre Abhängigkeit entsteht, wenn Modul A in seiner sequence Modul B referenziert, und Modul B gleichzeitig, direkt oder über eine Kette weiterer Module, wieder Modul A referenziert. Das typische Symptom ist ein fehlschlagender bin/magento setup:upgrade mit einer Fehlermeldung über eine nicht auflösbare Modulreihenfolge, oder ein Modul, das sich im Admin-Panel als "invalid" markiert zeigt, ohne dass der eigentliche Grund sofort ersichtlich ist.

Der erste Diagnoseschritt ist bin/magento module:status, das die tatsächlich berechnete Ladereihenfolge anzeigt. Zusätzlich lohnt sich ein Blick in die generierte app/etc/config.php: Dort erscheinen alle aktiven Module in der final berechneten Reihenfolge als nummeriertes Array. Wenn zwei eng verwandte, eigentlich voneinander abhängige Module dort in einer unerwarteten Reihenfolge oder mit widersprüchlichen Positionen auftauchen, ist das ein starkes Indiz für eine zirkuläre oder zumindest widersprüchliche sequence-Deklaration.


# Check the computed module load order and any warnings
bin/magento module:status

# Typical circular dependency error during compilation:
# Sequence for module "Vendor_ModuleA" is invalid ->
# a circular reference to module "Vendor_ModuleB" was detected

# Inspect the final computed module order (sequence numbers reflect load order)
grep -A 200 "'modules' => \[" app/etc/config.php | head -60

# Composer-level check: does the dependency graph even make sense on that layer?
bin/composer why vendor/module-a
bin/composer depends vendor/module-b

Wichtig ist die Unterscheidung zwischen einer echten Sequence-Zirkularität, die Magento beim Kompilieren explizit erkennt und mit einem Fehler quittiert, und einer subtileren Variante: zwei Module ohne direkte sequence-Zirkularität, die sich aber gegenseitig im PHP-Code über Konstruktor-Injektion referenzieren. Letzteres erzeugt keinen Magento-Fehler beim setup:upgrade, führt aber zu einem Dependency-Injection-Zirkel, der erst beim ersten Instanziierungsversuch der betroffenen Klassen mit einem "Circular dependency" Fehler aus dem Object Manager auffällt.

5. Zirkuläre Abhängigkeiten auflösen

Die nachhaltige Lösung für eine zirkuläre Abhängigkeit zwischen zwei fachlich eng verwandten Modulen orientiert sich am Vorbild, das Magento selbst in seiner Core-Architektur konsequent verwendet: die Trennung eines Moduls in ein *Api-Modul mit reinen Interfaces und Service Contracts, und ein Implementierungsmodul, das dieses Api-Modul referenziert. Ein Beispiel aus dem Magento-Core ist die Trennung von Magento_CatalogApi und Magento_Catalog: Andere Module referenzieren nur die Interfaces aus CatalogApi, ohne eine direkte Abhängigkeit zur konkreten Implementierung aufzubauen.

Übertragen auf ein zirkuläres Abhängigkeitsproblem zwischen Vendor_ModuleA und Vendor_ModuleB bedeutet das: Die gemeinsam benötigten Schnittstellen, etwa ein Service Contract, den beide Module gegenseitig aufrufen wollen, werden in ein drittes, neutrales Modul Vendor_SharedApi extrahiert, das selbst keine Abhängigkeit zu A oder B hat. Sowohl Vendor_ModuleA als auch Vendor_ModuleB referenzieren nur noch Vendor_SharedApi in ihrer sequence und ihrer composer.json, die direkte gegenseitige Abhängigkeit entfällt vollständig. Diese Auflösung ist der einzige wirklich saubere Weg, weil sie das zugrunde liegende Architekturproblem behebt, statt nur ein Symptom durch Umsortieren von sortOrder- oder sequence-Werten zu kaschieren.


<!-- Vendor_SharedApi/etc/module.xml: no dependency on ModuleA or ModuleB -->
<module name="Vendor_SharedApi"/>

<!-- Vendor_ModuleA/etc/module.xml: depends only on the shared API -->
<module name="Vendor_ModuleA">
    <sequence>
        <module name="Vendor_SharedApi"/>
    </sequence>
</module>

<!-- Vendor_ModuleB/etc/module.xml: depends only on the shared API too -->
<module name="Vendor_ModuleB">
    <sequence>
        <module name="Vendor_SharedApi"/>
    </sequence>
</module>

6. Optionale Abhängigkeit statt harter Sequence

Nicht jede Beziehung zwischen zwei Modulen muss eine harte Modulabhängigkeit sein. Wenn Modul B nur reagieren soll, falls Modul A zufällig installiert ist, aber ohne A vollständig eigenständig funktionsfähig bleibt, ist eine weiche Abhängigkeit über Magento\Framework\Module\Manager::isEnabled() der richtige Ansatz statt einer harten sequence-Deklaration mit begleitendem composer require.

Diese Technik erlaubt es, im Konstruktor ausschließlich auf Interfaces zu injizieren, die in jedem Fall existieren, und die tatsächliche Interaktion mit dem optionalen Modul erst zur Laufzeit bedingt auszuführen. Wichtig dabei: Der PHP-Code darf niemals eine Klasse aus dem optionalen Modul direkt im Konstruktor typisieren, da der Autoloader sonst beim Fehlen des Moduls sofort einen Fatal Error wirft, unabhängig davon, ob isEnabled() später korrekt false zurückgeben würde.


declare(strict_types=1);

namespace Vendor\PriceSync\Model;

use Magento\Framework\Module\Manager as ModuleManager;
use Psr\Log\LoggerInterface;

/**
 * Notifies the optional loyalty module about a price change, if it is installed.
 */
final class LoyaltyNotifier
{
    private const string LOYALTY_MODULE_NAME = 'Vendor_LoyaltyPoints';

    /**
     * @param ModuleManager $moduleManager Checks whether the optional module is enabled
     * @param LoggerInterface $logger Logs a debug notice when the module is absent
     */
    public function __construct(
        private readonly ModuleManager $moduleManager,
        private readonly LoggerInterface $logger,
    ) {
    }

    /**
     * Recalculates loyalty points only if the optional module is present and enabled.
     *
     * @param int $productId Product entity ID whose price just changed
     * @return void
     */
    public function notifyPriceChanged(int $productId): void
    {
        if (!$this->moduleManager->isEnabled(self::LOYALTY_MODULE_NAME)) {
            $this->logger->debug('Loyalty module not installed, skipping recalculation.');
            return;
        }

        // Resolve the optional dependency lazily via ObjectManager,
        // never via constructor type-hinting, to avoid a hard autoload dependency.
        $loyaltyService = \Magento\Framework\App\ObjectManager::getInstance()
            ->get(\Vendor\LoyaltyPoints\Api\RecalculationServiceInterface::class);
        $loyaltyService->recalculateForProduct($productId);
    }
}

Diese Konstruktion nutzt bewusst den Object Manager als bekannten, dokumentierten Ausnahmefall, weil eine klassische Konstruktor-Injektion hier den gewünschten optionalen Charakter der Abhängigkeit zunichtemachen würde. Für alle harten Modul-Abhängigkeiten bleibt Konstruktor-Injektion selbstverständlich der Standard, dieser Ausnahmefall gilt ausschließlich für echte, laufzeitgeprüfte optionale Abhängigkeiten.

7. Best Practices für eigene und Third-Party-Module

Die wichtigste Regel für saubere Modulabhängigkeiten lautet: minimale sequence-Deklaration. Ein Modul sollte in seiner sequence ausschließlich die Module referenzieren, deren etc-Merge-Reihenfolge tatsächlich relevant ist, nicht jede entfernte, indirekte Abhängigkeit. Überflüssige sequence-Einträge erhöhen unnötig die Komplexität des Abhängigkeitsgraphen und erschweren spätere Refactorings, ohne einen funktionalen Nutzen zu bieten.

Ebenso wichtig: Code sollte niemals implizite Annahmen über die Ladereihenfolge treffen, die nicht durch eine explizite sequence-Deklaration abgesichert sind. Wer beispielsweise in einem Observer davon ausgeht, dass ein bestimmter anderer Event-Handler bereits gelaufen ist, ohne dies über eine echte sequence-Abhängigkeit sicherzustellen, baut eine fragile, implizite Modulabhängigkeit, die bei der kleinsten Änderung an einem der beteiligten Module unbemerkt bricht. Für Third-Party-Module gilt zusätzlich: niemals vendor-Dateien direkt patchen, um eine fehlende Sequence zu ergänzen, sondern immer über ein eigenes, kleines Erweiterungsmodul mit korrekter sequence-Referenz auf das Fremdmodul.

8. Abhängigkeitsgraph sichtbar machen

bin/magento module:status liefert die berechnete Ladereihenfolge, aber nicht den vollständigen Abhängigkeitsgraphen mit allen Beziehungen zwischen Modulen. Für die Composer-Ebene liefern bin/composer why <paket> und bin/composer depends <paket> eine Übersicht, welche anderen Pakete ein bestimmtes Paket referenzieren beziehungsweise wovon es selbst abhängt. Diese beiden Befehle zusammen ergeben ein vollständiges Bild sowohl der Composer-Paketebene als auch der Magento-internen Ladereihenfolge.

Bei komplexeren Projekten mit zwanzig oder mehr eigenen Modulen lohnt sich eine einmalige, dokumentierte Visualisierung des Abhängigkeitsgraphen, etwa als einfaches Diagramm, das für jedes Modul seine sequence-Ziele und seine Composer-require-Ziele nebeneinander zeigt. Diese Dokumentation macht auf einen Blick sichtbar, wo eine Modulabhängigkeit nur auf einer der beiden Ebenen existiert, ein häufiges Anzeichen für eine unvollständige oder inkonsistente Deklaration.


# Which installed packages depend on this one?
bin/composer why vendor/module-shared-api

# What does this package itself depend on?
bin/composer depends vendor/module-a

# Cross-check: does every composer require have a matching module.xml sequence entry?
grep -r "vendor/module" app/code/Vendor/ModuleA/composer.json
grep -r "Vendor_" app/code/Vendor/ModuleA/etc/module.xml

9. Vergleich: sequence vs. composer require vs. soft dependency

Die drei vorgestellten Mechanismen lösen unterschiedliche Aufgaben und sollten nicht gegeneinander ausgetauscht werden. Die folgende Übersicht fasst zusammen, wann welcher Mechanismus die richtige Wahl für eine Modul-Abhängigkeit ist.

Mechanismus Steuert Typischer Fehler Richtiger Einsatz
module.xml sequence Ladereihenfolge der etc-Merges Referenz ohne begleitendes composer require Immer mit composer require kombinieren
composer.json require Paketinstallation Ohne sequence, obwohl Ladereihenfolge relevant ist Bei direkter Klassennutzung im Code
composer.json suggest Empfehlung ohne Installation Direkte Klassenreferenz ohne isEnabled-Check Immer mit Module\Manager::isEnabled() absichern
Gemeinsames Api-Modul Auflösung zirkulärer Abhängigkeiten Interfaces bleiben im Implementierungsmodul Shared Interfaces in neutrales drittes Modul extrahieren

Diese Tabelle zeigt: Jede unsaubere Kombination aus den drei Mechanismen erzeugt einen typischen, wiederkehrenden Fehler. Die konsequente, korrekte Kombination aus sequence, require und, wo passend, einer laufzeitgeprüften weichen Abhängigkeit verhindert praktisch alle in diesem Artikel beschriebenen Probleme rund um Modulabhängigkeiten.

10. Zusammenfassung

Saubere Modul-Abhängigkeiten in Magento erfordern die klare Trennung zweier Ebenen: composer.json steuert die Paketinstallation, module.xml sequence steuert die Ladereihenfolge der Konfigurationsdateien. Jede sequence-Referenz sollte von einem passenden composer require begleitet werden, außer bei bewusst weichen, laufzeitgeprüften Abhängigkeiten über Module\Manager::isEnabled(). Zirkuläre Abhängigkeiten entstehen, wenn zwei Module sich direkt oder über eine Kette gegenseitig referenzieren, und lassen sich zuverlässig nur durch Extraktion gemeinsamer Schnittstellen in ein neutrales, drittes Api-Modul dauerhaft auflösen.

Der langfristige Nutzen einer sauberen Modulabhängigkeiten-Strategie zeigt sich vor allem bei wachsenden Projekten mit vielen eigenen Erweiterungsmodulen: Statt bei jedem neuen Modul ein Risiko für zirkuläre Referenzen einzugehen, sorgt eine klare Architektur mit Shared-Api-Modulen, minimalen sequence-Deklarationen und dokumentierten Composer-Abhängigkeiten dafür, dass neue Module sich vorhersehbar und ohne Überraschungen in den bestehenden Abhängigkeitsgraphen einfügen.

Magento Modul-Abhängigkeiten: Das Wichtigste auf einen Blick

Zwei Ebenen trennen

composer.json steuert die Installation, module.xml sequence steuert die Ladereihenfolge. Beide gehören zusammen, nicht getrennt.

Weiche Abhängigkeiten absichern

suggest niemals ohne Module\Manager::isEnabled()-Check verwenden, sonst Fatal Error bei fehlendem Modul.

Zirkuläre Abhängigkeiten auflösen

Gemeinsame Interfaces in ein neutrales Shared-Api-Modul extrahieren, statt sortOrder-Tricks zu nutzen.

Diagnose

module:status, composer why und composer depends zusammen ergeben den vollständigen Abhängigkeitsgraphen.

11. FAQ: Magento Modul-Abhängigkeiten

1require vs. sequence, was ist der Unterschied?
require steuert die Installation, sequence die Ladereihenfolge. Zwei getrennte Ebenen, die bei echter Abhängigkeit zusammengehören.
2Reicht composer require für die Ladereihenfolge?
Nein, dafür ist zusätzlich eine sequence-Deklaration in module.xml nötig.
3require vs. suggest?
require installiert zwingend, suggest zeigt nur einen Hinweis. suggest-Nutzung im Code braucht isEnabled()-Check.
4Zirkuläre Abhängigkeit erkennen?
module:status und app/etc/config.php zeigen die berechnete Reihenfolge und mögliche Widersprüche.
5Zirkuläre Abhängigkeit auflösen?
Gemeinsame Schnittstellen in ein drittes, neutrales Api-Modul extrahieren.
6Wann optionale Abhängigkeit?
Wenn das Modul auch ohne das andere voll funktionsfähig bleibt. Mit Module\Manager::isEnabled() prüfen.
7vendor-Dateien patchen erlaubt?
Nein, geht bei composer update verloren. Eigenes Erweiterungsmodul mit korrekter sequence anlegen.
8Vollständigen Abhängigkeitsgraphen finden?
module:status plus composer why und composer depends ergeben beide Ebenen zusammen.
9Namenreferenz im Konstruktor für optionales Modul?
Nein, führt bei fehlendem Modul zum Fatal Error. Klasse lazy über Object Manager nach isEnabled()-Check auflösen.
10Wie viele sequence-Einträge sind sinnvoll?
Nur so viele wie tatsächlich für die etc-Merge-Reihenfolge nötig sind, nicht jede indirekte Beziehung.

Mironsoft

Magento-Modularchitektur, Refactoring und langfristig wartbare Erweiterungen

Zirkuläre Abhängigkeiten in eurem Modul-Stack?

Wir analysieren bestehende Magento-Module, decken zirkuläre und inkonsistente Abhängigkeiten auf und refaktorieren sie in eine saubere, langfristig wartbare Modularchitektur.

Architektur-Review

Vollständigen Abhängigkeitsgraphen eurer Module dokumentieren

Refactoring

Zirkuläre Abhängigkeiten durch Shared-Api-Module dauerhaft auflösen

Modul-Entwicklung

Neue Erweiterungsmodule von Anfang an mit sauberer Abhängigkeitsstruktur