Coding Standard, Dependency und Legacy im Griff
Statische Tests decken in Magento 2 genau die Fehlerklasse auf, die weder PHPStan noch PHPUnit sehen: Verstöße gegen den Magento Coding Standard, undeklarierte Abhängigkeiten zwischen Modulen und schleichend wieder eingeschleppte Legacy-Patterns. Wer die eigene Suite unter dev/tests/static ernst nimmt, verhindert genau die Art von strukturellen Fehlern, die erst beim nächsten Major-Update oder bei einer geänderten Modul-Ladereihenfolge sichtbar werden.
Inhaltsverzeichnis
- 1. Statische Tests in Magento 2: Einordnung und Abgrenzung
- 2. Magento Coding Standard: phpcs-Ruleset und Sniff-Kategorien
- 3. phpcs.xml einrichten und Magento Coding Standard ausführen
- 4. Der Dependency-Test: undeklarierte Modulabhängigkeiten aufdecken
- 5. Modul-Sequenzen in module.xml richtig deklarieren
- 6. Legacy-Test: veraltete Magento-Patterns automatisch erkennen
- 7. Aufbau von dev/tests/static/testsuite und Ausführung
- 8. Statische Tests in die CI-Pipeline einbinden
- 9. Vergleich: Coding Standard, Dependency Test, Legacy Test und PHPStan
- 10. Zusammenfassung
- 11. FAQ
1. Statische Tests in Magento 2: Einordnung und Abgrenzung
Statische Tests sind in Magento 2 keine einzelne Datei und kein einzelnes Werkzeug, sondern eine eigene, von Adobe direkt im Core mitgelieferte Testkategorie unter dev/tests/static. Anders als PHPUnit-Tests, die Verhalten zur Laufzeit prüfen, laufen statische Tests ohne eine einzige Zeile Code auszuführen: sie parsen Quelldateien, XML-Konfigurationen und Layout-Dateien und werten sie strukturell aus. Diese statischen Tests sind fest in drei Kernbereiche gegliedert, die in diesem Artikel im Zentrum stehen: der Magento Coding Standard über phpcs, der Dependency-Test gegen undeklarierte Modulabhängigkeiten und der Legacy-Test gegen veraltete Code-Patterns.
Wichtig ist die Abgrenzung zu zwei benachbarten, aber klar getrennten Werkzeugen. PHPStan ist ein externes Analyse-Tool, das Typinformationen und Datenflüsse prüft und über eigene Level und Baselines konfiguriert wird, das ist bewusst nicht Thema dieses Artikels. PHPUnit wiederum prüft mit Unit- und Integrationstests tatsächliches Laufzeitverhalten von Klassen und Modulen. Statische Tests dagegen prüfen weder Typen noch Verhalten, sondern Konventionen, Strukturintegrität und die Einhaltung des Magento Coding Standard, unabhängig davon, ob der Code beim Ausführen überhaupt funktioniert.
Der praktische Wert dieser statischen Tests liegt genau in dieser Nische: Der Dependency-Test findet zum Beispiel ein Modul, das sich beim lokalen Testen völlig normal verhält, aber bei einer anderen Modul-Ladereihenfolge in der Produktion bricht, weil eine Abhängigkeit nie deklariert wurde. Der Magento Coding Standard wiederum erzwingt Konventionen, die für die Wartbarkeit über Jahre entscheidend sind, etwa den Verzicht auf direkte ObjectManager-Aufrufe. Kein anderes Werkzeug in der Magento-Toolchain deckt genau diese Kombination aus Konventions- und Strukturprüfung ab.
2. Magento Coding Standard: phpcs-Ruleset und Sniff-Kategorien
Der Magento Coding Standard wird technisch über das Composer-Paket magento/magento-coding-standard ausgeliefert, ein Regelwerk für PHP_CodeSniffer, das Adobe selbst für den Core und für zertifizierte Extensions verwendet. Wer phpcs --standard=Magento2 gegen ein eigenes Modul laufen lässt, prüft damit exakt die Konventionen, die auch bei der Marketplace-Technical-Review angewendet werden. Der Magento Coding Standard ist dabei deutlich strenger als generische PHP-Standards wie PSR-12, weil er zusätzlich Magento-spezifisches Wissen einbringt.
Die Sniffs im Magento Coding Standard sind in fachliche Kategorien gruppiert: Security-Sniffs erkennen unsichere Konstrukte wie direkte SQL-String-Konkatenation, Performance-Sniffs flaggen ineffiziente Muster wie Datenbankzugriffe in Schleifen, und PHP-Sniffs erzwingen etwa den Verzicht auf @-Error-Silencing oder var_dump in produktivem Code. Eine eigene Kategorie widmet sich reinen Magento-Konventionen, etwa dass Konstruktoren keine direkte ObjectManager::getInstance()-Instanziierung enthalten dürfen, sondern konsequent Dependency Injection nutzen müssen.
Für eigene Module lohnt sich zusätzlich die Kombination mit dem Magento2-Doc-Comments-Sniff für vollständige PHPDoc-Blöcke, weil viele Teams intern strengere Dokumentationsanforderungen haben als der Standard-Ruleset vorschreibt. Die Installation läuft über eine reguläre Composer-Dev-Abhängigkeit, wodurch der Magento Coding Standard versioniert mit dem Projekt mitwächst.
{
"require-dev": {
"magento/magento-coding-standard": "^32.0",
"squizlabs/php_codesniffer": "^3.9"
},
"extra": {
"magento-force": "override"
}
}
3. phpcs.xml einrichten und Magento Coding Standard ausführen
Damit der Magento Coding Standard nicht bei jedem Aufruf erneut per Kommandozeilen-Flag angegeben werden muss, legt man im Modul- oder Projektwurzelverzeichnis eine phpcs.xml an. Diese Datei referenziert den Ruleset-Namen Magento2, definiert die zu prüfenden Dateiendungen wie php und phtml und schließt Ordner wie vendor oder generierte Verzeichnisse explizit aus. Ohne eine solche Konfiguration prüft phpcs entweder zu viel, etwa Drittanbieter-Code, oder zu wenig, weil Template-Dateien mit der Endung .phtml vergessen werden.
Ein zweiter wichtiger Baustein ist der installed_paths-Eintrag, der PHP_CodeSniffer mitteilt, wo das Ruleset des Magento Coding Standard physisch liegt. Bei einer Composer-Installation übernimmt das Paket dies in der Regel automatisch über die eigene Composer\Installer-Integration, bei manuellen Setups muss der Pfad explizit über phpcs --config-set installed_paths gesetzt werden. Fehlt dieser Schritt, bricht phpcs mit einer Fehlermeldung ab, dass der Ruleset-Name Magento2 unbekannt sei.
<!-- phpcs.xml im Projekt- oder Modulwurzelverzeichnis -->
<?xml version="1.0"?>
<ruleset name="Mironsoft Magento Coding Standard">
<description>Runs the Magento Coding Standard against custom modules</description>
<!-- Reference the official Magento2 ruleset -->
<rule ref="Magento2"/>
<file>app/code/Mironsoft</file>
<arg name="extensions" value="php,phtml"/>
<arg name="colors"/>
<arg value="p"/>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/var/*</exclude-pattern>
<exclude-pattern>*/generated/*</exclude-pattern>
</ruleset>
Im laufenden Betrieb reicht danach ein einziger Aufruf über den Docker-Wrapper aus, um den kompletten Magento Coding Standard gegen das eigene Modul laufen zu lassen. Für Mark-Shust-Setups bietet sich ein eigener Wrapper wie bin/phpcs an, der intern gegen vendor/bin/phpcs im Container delegiert.
#!/usr/bin/env bash
# Run the Magento Coding Standard against a specific module
bin/phpcs app/code/Mironsoft/SeoSuite --standard=Magento2
# Auto-fix violations that phpcbf can safely resolve
bin/phpcbf app/code/Mironsoft/SeoSuite --standard=Magento2
# Run Magento's own static test suite (Dependency, Legacy, coding style)
bin/cli vendor/bin/phpunit -c dev/tests/static/phpunit.xml.dist \
--filter "Magento\\Test\\Integrity\\Dependency"
bin/cli vendor/bin/phpunit -c dev/tests/static/phpunit.xml.dist \
--filter "Magento\\Test\\Legacy"
4. Der Dependency-Test: undeklarierte Modulabhängigkeiten aufdecken
Der Dependency-Test ist einer der drei Kernbausteine der statischen Tests und prüft eine Frage, die weder der Magento Coding Standard noch PHPStan beantworten kann: Nutzt ein Modul Klassen, Blöcke, Layout-Handles oder Events eines anderen Moduls, ohne diese Abhängigkeit formal in module.xml zu deklarieren? Dazu durchsucht der Test statisch den kompletten PHP-, XML- und Template-Code eines Moduls, extrahiert jede Referenz auf fremde Klassen und Namespaces und gleicht sie mit den deklarierten Sequenzen ab.
Ein typischer Fall: Ein eigenes Modul injiziert in einem Konstruktor eine Klasse aus Magento\ConfigurableProduct, ohne dass Magento_ConfigurableProduct in der eigenen module.xml als Sequenz-Abhängigkeit gelistet ist. Lokal funktioniert das problemlos, weil beide Module ohnehin installiert sind und die Ladereihenfolge zufällig passt. Sobald sich aber die Modul-Reihenfolge ändert, etwa durch ein Update oder eine andere Modulkombination, kann die referenzierte Klasse zum Zeitpunkt des Ladens noch nicht verfügbar sein. Der Dependency-Test schlägt genau hier präventiv Alarm, lange bevor ein solcher Fehler live auftritt.
Praktisch meldet der Dependency-Test jeden Verstoß mit exaktem Dateipfad, Zeilennummer und dem Namen der undeklarierten Fremdklasse. Das macht die Fehlerbehebung eindeutig: entweder wird die fehlende Sequenz in module.xml ergänzt, oder die Abhängigkeit wird bewusst über eine Service-Contract-Schnittstelle statt einer konkreten Modellklasse aufgelöst, um die Kopplung von vornherein zu reduzieren.
5. Modul-Sequenzen in module.xml richtig deklarieren
Die module.xml jedes Magento-2-Moduls kennt neben dem reinen Namen auch einen sequence-Block, der festlegt, nach welchen anderen Modulen das eigene Modul geladen werden muss. Das ist keine Bestätigung einer Composer-Abhängigkeit, sondern eine eigenständige, für den Dependency-Test relevante Information: Composer regelt, welche Pakete überhaupt installiert werden, module.xml regelt, in welcher Reihenfolge sie innerhalb von Magento initialisiert werden. Beides muss synchron gehalten werden, ansonsten meldet der Dependency-Test weiterhin eine Lücke, selbst wenn Composer die richtige Version zieht.
Fehlt eine Sequenz, kann das zwei sichtbare Folgen haben: Die Dependency Injection Konfiguration in di.xml wird in einer Reihenfolge zusammengeführt, die nicht der fachlichen Erwartung entspricht, oder setup:upgrade-Skripte laufen vor den Skripten des Moduls, von dem sie eigentlich abhängen. Eine korrekt gepflegte Sequenz ist deshalb keine bürokratische Formalie, sondern die Grundlage dafür, dass Magentos Modulsystem deterministisch und reproduzierbar bleibt.
<!-- app/code/Mironsoft/SeoSuite/etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Mironsoft_SeoSuite">
<sequence>
<!-- Declares that Magento_Catalog must load before this module -->
<module name="Magento_Catalog"/>
<!-- Declares that Magento_ConfigurableProduct must load before this module -->
<module name="Magento_ConfigurableProduct"/>
<module name="Mironsoft_Core"/>
</sequence>
</module>
</config>
Eine bewährte Praxis ist es, die Sequenz nach jedem neuen Konstruktor-Import oder jeder neuen Layout-Referenz manuell zu überprüfen, statt sie nur einmal beim Modul-Anlegen zu pflegen. In der Praxis reicht dafür ein regelmäßiger Lauf des Dependency-Tests direkt während der Entwicklung, nicht erst kurz vor dem Release, weil sich Abhängigkeiten mit jeder neuen Funktion verschieben können.
6. Legacy-Test: veraltete Magento-Patterns automatisch erkennen
Der Legacy-Test ist der dritte Baustein der statischen Tests und prüft, ob ein Modul Klassen, Methoden oder Muster verwendet, die Adobe offiziell als veraltet oder als Altlast aus Magento 1 eingestuft hat. Anders als der Dependency-Test geht es hier nicht um fehlende Deklarationen, sondern um bewusst verbotene oder auslaufende Konstrukte, die in einer zukünftigen Major-Version ohne Vorwarnung entfernt werden können. Der Legacy-Test hält dazu intern gepflegte Listen veralteter Klassen, Methoden und Verzeichnisstrukturen vor und vergleicht den eigenen Code dagegen.
Ein klassisches Beispiel ist die direkte Nutzung des ObjectManagers innerhalb eines Konstruktors, statt die benötigte Abhängigkeit sauber per Dependency Injection zu übergeben. Dieses Muster funktioniert technisch weiterhin, gilt aber als Legacy-Pattern, weil es Tests erschwert, versteckte Abhängigkeiten erzeugt und dem gesamten Prinzip der Service Contracts widerspricht, auf dem Magento 2 aufbaut. Der Legacy-Test markiert solche Stellen zuverlässig, lange bevor sie in einem Code-Review manuell auffallen würden.
<?php
declare(strict_types=1);
namespace Mironsoft\SeoSuite\Model;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Api\ProductRepositoryInterface;
/**
* Flagged by the Legacy test: direct ObjectManager usage inside a constructor.
*/
class MetaBuilderLegacy
{
public function build(int $productId): string
{
// Legacy pattern: bypasses constructor injection entirely.
$repository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
$product = $repository->getById($productId);
return (string) $product->getMetaTitle();
}
}
/**
* Fixed version: dependency is injected through the constructor.
*/
class MetaBuilderFixed
{
/**
* @param ProductRepositoryInterface $productRepository Injected repository, no ObjectManager needed.
*/
public function __construct(
private readonly ProductRepositoryInterface $productRepository
) {
}
public function build(int $productId): string
{
$product = $this->productRepository->getById($productId);
return (string) $product->getMetaTitle();
}
}
Neben dem ObjectManager-Missbrauch prüft der Legacy-Test unter anderem auf verbliebene Magento-1-Aufrufe wie Mage::, auf als veraltet markierte Klassenkonstanten und auf Verzeichnisstrukturen, die dem alten Modulsystem entstammen. Für Migrationsprojekte, die alten Custom-Code in eine neue Magento-2-Codebasis überführen, ist der Legacy-Test oft der erste harte Realitätscheck, der zeigt, wie viel tatsächlich modernisiert werden muss.
7. Aufbau von dev/tests/static/testsuite und Ausführung
Alle statischen Tests laufen technisch über eine ganz normale PHPUnit-Testsuite, die unter dev/tests/static/testsuite/Magento/Test/ liegt und über dev/tests/static/phpunit.xml.dist konfiguriert wird. Innerhalb dieses Verzeichnisses gibt es klar getrennte Unterordner: Php/ enthält unter anderem den LiveCodeTest, der intern den Magento Coding Standard über phpcs ausführt, Integrity/Dependency/ enthält den Dependency-Test, und Legacy/ enthält den Legacy-Test samt seiner Ausnahme- und Referenzlisten.
Wichtig für eigene Module ist, dass diese Tests standardmäßig über Whitelist- beziehungsweise Blacklist-Konfigurationsdateien gesteuert werden, welche Pfade überhaupt geprüft werden. Ein neu angelegtes Modul unter app/code/Mironsoft wird automatisch erfasst, solange es dem Standard-Namespace-Schema folgt, spezielle Ausschlüsse lassen sich aber über eigene Konfigurationsdateien im Testsuite-Verzeichnis ergänzen, etwa um generierten oder bewusst noch nicht migrierten Code temporär auszunehmen.
Ausgeführt werden die statischen Tests wie jede andere PHPUnit-Suite, nur mit einer eigenen Konfigurationsdatei als Ziel. Da ein voller Lauf über alle Unterordner mehrere Minuten dauern kann, filtert man in der täglichen Entwicklung meist gezielt nach einer einzelnen Testklasse, etwa nur nach dem Dependency-Test oder nur nach dem Legacy-Test, statt jedes Mal die komplette Suite zu starten.
8. Statische Tests in die CI-Pipeline einbinden
In einer CI-Pipeline lohnt sich eine zweistufige Strategie für statische Tests. Die erste Stufe prüft bei jedem Commit oder Merge-Request nur die geänderten Dateien mit einem schnellen phpcs-Lauf gegen den Magento Coding Standard, was in der Regel wenige Sekunden dauert und sofortiges Feedback liefert. Die zweite Stufe führt die vollständige Suite unter dev/tests/static/testsuite aus, inklusive Dependency-Test und Legacy-Test über die gesamte Codebasis, entweder vor jedem Merge in den Hauptbranch oder als nächtlicher Lauf.
Diese Aufteilung ist bewusst gewählt, weil der Dependency-Test naturgemäß den gesamten Modulgraphen betrachten muss, um verlässliche Aussagen zu treffen, während ein reiner Coding-Standard-Check pro Datei isoliert funktioniert. Eine Pipeline, die ausschließlich geänderte Dateien gegen den Dependency-Test prüft, würde Abhängigkeiten übersehen, die durch Änderungen an ganz anderer Stelle im Modul entstanden sind.
Entscheidend ist außerdem, dass die Pipeline bei einem Verstoß den Merge tatsächlich blockiert, nicht nur eine Warnung ausgibt. Statische Tests entfalten ihren vollen Wert nur als hartes Gate: ein Team, das Verstöße gegen den Magento Coding Standard oder Dependency-Test-Fehler als optionale Hinweise behandelt, sammelt über Monate genau die technischen Schulden an, die diese Tests eigentlich verhindern sollen.
9. Vergleich: Coding Standard, Dependency Test, Legacy Test und PHPStan
Alle drei Bausteine der statischen Tests prüfen unterschiedliche Aspekte derselben Codebasis und ergänzen sich, statt sich zu überschneiden. Der Magento Coding Standard sichert Konventionen und Stil, der Dependency-Test sichert die strukturelle Integrität des Modulsystems, und der Legacy-Test sichert die Zukunftsfähigkeit gegenüber kommenden Magento-Versionen. PHPStan wird an dieser Stelle nur zur Einordnung erwähnt: es prüft Typinformationen und Datenflüsse und ist damit ein eigenständiges Thema außerhalb dieses Artikels.
| Prüfebene | Werkzeug | Prüft | Ausführung |
|---|---|---|---|
| Coding Standard | phpcs + Magento2-Ruleset |
Stil, Security- und Performance-Konventionen pro Datei | Sekunden |
| Dependency Test | dev/tests/static (PHPUnit) |
Undeklarierte Modulabhängigkeiten über module.xml | Sekunden bis Minuten |
| Legacy Test | dev/tests/static (PHPUnit) |
Veraltete Klassen, Methoden und Magento-1-Patterns | Sekunden bis Minuten |
| PHPStan (Kontrast) | PHPStan | Typsicherheit und Datenflüsse, separates Thema | Sekunden bis Minuten |
Die praktische Konsequenz: keiner dieser statischen Tests ersetzt einen anderen. Ein Modul kann den Magento Coding Standard vollständig einhalten und trotzdem den Dependency-Test wegen einer fehlenden Sequenz reißen, oder umgekehrt strukturell sauber sein und dennoch Legacy-Patterns enthalten. Erst die Kombination aller drei Prüfungen aus dev/tests/static ergibt ein vollständiges Bild der strukturellen und konventionellen Qualität eines Magento-2-Moduls.
Mironsoft
Magento Coding Standard, Dependency-Checks und CI-Integration für Magento 2
Statische Tests von Anfang an sauber aufsetzen?
Wir richten den Magento Coding Standard, den Dependency-Test und den Legacy-Test für eure Module ein, von der phpcs.xml über korrekte module.xml-Sequenzen bis zur harten CI-Gate-Integration.
Coding Standard
phpcs.xml und Magento Coding Standard sauber konfigurieren
Dependency Test
module.xml-Sequenzen korrigieren und dauerhaft absichern
CI-Gate
Statische Tests als hartes Merge-Gate in eure Pipeline einbauen
10. Zusammenfassung
Statische Tests unter dev/tests/static decken in Magento 2 eine Lücke ab, die weder PHPStan noch PHPUnit schließen können: die Einhaltung des Magento Coding Standard, die strukturelle Integrität des Modulsystems über den Dependency-Test und die Zukunftsfähigkeit des Codes über den Legacy-Test. Wer diese drei Prüfungen konsequent in den Entwicklungsalltag integriert, statt sie nur einmalig vor einem Release laufen zu lassen, verhindert genau die Fehlerklasse, die sonst erst bei einer geänderten Modul-Reihenfolge oder einem Major-Update sichtbar wird.
Der entscheidende Hebel ist eine saubere phpcs.xml-Konfiguration für den Magento Coding Standard, gepflegte Sequenzen in jeder module.xml und ein CI-Gate, das Verstöße tatsächlich blockiert statt nur zu protokollieren. Kombiniert mit einer zweistufigen Pipeline, schneller Coding-Standard-Check pro Commit und vollständiger Dependency- und Legacy-Prüfung vor jedem Merge, werden statische Tests zu einem verlässlichen Fundament, auf dem sich weitere Qualitätssicherung wie PHPStan und PHPUnit erst sinnvoll aufbauen lässt.
Statische Tests in Magento 2: Das Wichtigste auf einen Blick
Magento Coding Standard
phpcs-Ruleset Magento2 aus magento/magento-coding-standard, prüft Stil, Security und Performance.
Dependency Test
Findet undeklarierte Modulabhängigkeiten gegenüber den Sequenzen in module.xml.
Legacy Test
Erkennt veraltete Patterns wie direkte ObjectManager-Aufrufe oder Magento-1-Reste.
CI-Betrieb
Schneller Coding-Standard-Check pro Commit, vollständige Suite als hartes Gate vor jedem Merge.