Search by

lenzebusiness / shopware-extension-check

lenzebusiness

Various checks for shopware extensions

Package info

bitbucket.org/lenzebusiness/shopware-extension-check

pkg:composer/lenzebusiness/shopware-extension-check

Statistics

Installs: 2 896

Dependents: 0

Suggesters: 0

dev-main 2026-09-01 12:32 UTC

This package is auto-updated.

Last update: 2026-09-01 12:33:33 UTC


README

Statische Prüfungen für Shopware-Extensions. Läuft in den Pipelines aller Extensions und bricht den Build ab, wenn eine Prüfung anschlägt.

Der Schwerpunkt liegt auf Fehlern, die zur Laufzeit nichts melden — tote Twig- Overrides, nicht deklarierte Config-Keys, entfernte Include-Ziele. Genau die fallen bei einem Shopware-Minor-Upgrade sonst niemandem auf.

Verwendung

composer require --dev lenzebusiness/shopware-extension-check "dev-main"
vendor/bin/shopware-extension-check .

Das Argument zeigt auf die Wurzel der zu prüfenden Extension — dort, wo die composer.json liegt. Ein relativer Pfad wird gegen das aktuelle Arbeitsverzeichnis aufgelöst, ein absoluter direkt übernommen. Aus dem Shopware-Root heraus also:

vendor/bin/shopware-extension-check custom/plugins/MyExtension

Exit-Codes: 0 = keine Befunde, 1 = mindestens ein Befund.

In der Pipeline

So ist es in LenzPlatformCreditManagement eingebunden — ein eigener Step im parallel-Block der default-Pipeline:

definitions:
  steps:
    - step: &lenzebusinesscheck
        image: lenzebusiness/pipeline-php:8.2
        name: LENZ eBusiness internal checks.
        script:
          - composer require --dev lenzebusiness/shopware-extension-check "dev-main" shopware/storefront "6.7.*"
          - vendor/bin/shopware-extension-check .

pipelines:
  default:
    - parallel:
        steps:
          - step: *lenzebusinesscheck
          # ... weitere Checks
    - step: *shopware-plugin-build

shopware/storefront gehört mit in die composer require-Zeile. Ohne das Paket fehlen die Core-Templates zum Vergleich, und TwigTemplateOverride überspringt sich selbst — der Step ist dann grün, ohne die wichtigste Prüfung ausgeführt zu haben. Jeder Bitbucket-Step läuft in einem frischen Container, es hilft also nicht, dass ein anderer Step (etwa PHPStan) das Paket installiert.

Die Versionsangabe an die Extension anpassen: "6.7.*" für eine Extension mit "shopware/core": ">=6.7.0.0 <6.8.0.0". Alternativ den Pfad über SW_STOREFRONT_VIEWS setzen, siehe Storefront-Templates finden.

Die Checks laufen in der default-Pipeline, also bei jedem Push. Die tags-Pipeline baut und veröffentlicht nur — was bereits gebaut wird, war schon geprüft.

Funktionsweise

ShopwareExtensionCheck::check() läuft das Verzeichnis rekursiv ab und überspringt .git und vendor. Für jede Datei wird jede Klasse aus src/Inspection/Check/ instanziiert; schlägt isSupported() an, läuft inspect(). Befunde werden pro Datei gesammelt und am Ende gruppiert ausgegeben.

Vorab wird aus der composer.json der Extension die minimale Shopware-Version gelesen (Regex auf "shopware/core": ">=X.Y). Versions-abhängige Prüfungen laufen nur, wenn dieser Wert passt — aktuell exakt 6.7.

Aufbau:

bin/shopware-extension-check     Einsprungpunkt, rendert den Report
src/ShopwareExtensionCheck.php   Orchestrator: Walk, Discovery, Sammlung
src/Inspection/                  Vertrag: Interface, DataBag, Result, Finding
src/Inspection/Check/            die Prüfungen
src/Cli/ExtensionDirectory.php   löst das Verzeichnis-Argument auf
src/Twig/TwigSyntax.php          zustandslose Twig-Helfer (statisch)
src/Twig/TwigContext.php         Kontext pro Lauf, mit den Caches (Instanz)
tests/                           Fixture-Suite

Drei Eigenschaften, die man beim Schreiben einer Prüfung kennen muss:

  • Inspections werden einmal pro Lauf instanziiert, nicht pro Datei. Instanz-Properties sind also erlaubt und statischer Zustand unnötig. Alles, was pro Extension gecacht wird, liegt im TwigContext, den der InspectionDataBag mitführt — deshalb sieht ein zweiter Lauf im selben Prozess die Caches des ersten nicht.
  • Discovery ist gehärtet. Eine Datei in Check/, die keine instanziierbare Inspection ist (Trait, abstrakte Klasse, Notiz), wird übersprungen statt bei new zu fatalen. Aber: nur Inspections gehören dorthin.
  • Zwei Schweregrade. addError() bricht den Build, addWarning() nicht. Ein Error muss sicher sein — ein False Positive stoppt sonst die Pipeline aller Extensions. Eine Warnung ist für Befunde, die von innen nicht entscheidbar sind: ein Partial, das vielleicht das Kundentheme rendert, ein ignore missing-Include, das absichtlich ins Leere zeigt.

Am Ende steht immer eine Zeile N error(s), M warning(s). — auch bei null Befunden, damit man in einem Pipeline-Log sieht, dass tatsächlich geprüft wurde.

Befunde tragen eine Zeilennummer, wo eine existiert, und sind pro Datei nach Zeile sortiert:

Error in file: src/Resources/views/storefront/component/address/address-personal.html.twig
-> line 7: Block "component_address_form_salutation_select" does not exist ...
-> line 10: Config key "undeclaredOnLine10" is read but not declared ...

Regeln, die von der Abwesenheit einer Sache handeln (fehlendes declare(strict_types=1);) oder von einer ganzen Datei (ein Template, das nichts rendert), melden bewusst keine Zeile statt eine zu erfinden.

Der Checker sammelt nur; gerendert wird im Einsprungpunkt über getReport(). Bibliothekscode schreibt nichts auf stdout — auch die Hinweise des TwigContext (fehlende Storefront, Tippfehler in der Ignore-Datei) werden gesammelt.

Prüfungen

Storefront-Twig

Alle drei Prüfungen dieser Gruppe gelten nur für Templates unter src/Resources/views/. Nur die nehmen an der Twig-Theme-Inheritance teil. Templates unter src/Resources/app/administration/ sind Vue-Komponenten-Templates, die aus JavaScript importiert werden — auf die trifft nichts davon zu.

TwigTemplateOverride

Findet Twig-Overrides und Includes, die stillschweigend nichts tun. Der wichtigste Check im Tool, weil Twig hier keinerlei Fehler produziert:

BefundUrsache
Block "x" does not exist in the inheritance chain of "y"Der Blockname existiert in der Vererbungskette des erweiterten Templates nicht. Twig ignoriert die Definition — der Override rendert nie. Typisch nach einem Shopware-Upgrade, in dem Core den Block umbenannt oder entfernt hat.
sw_extends "x", which does not exist in Shopware coreDas erweiterte Template gibt es nicht mehr. Damit rendert die ganze Datei nie.
Includes "x", which does not existsw_include-Ziel existiert nicht. Das wirft erst auf der Seite, die es tatsächlich rendert — kann also lange unentdeckt bleiben.

Zusätzlich als Warnung:

BefundUrsache
Includes "x" with "ignore missing" and it resolves nowhereKein Fehler, ignore missing ist Absicht. Aber die Meldung nennt die optionalen Integrationen, die in diesem Checkout inaktiv sind — und sie ist der einzige Weg, einen Tippfehler in so einem Pfad zu bemerken, denn Twig rendert dann stillschweigend nichts.

Wichtige Details:

  • Verglichen wird gegen die konkret erweiterte Vererbungskette, nicht global gegen alle Core-Templates. Ein globaler Vergleich erzeugt falsche Negative.
  • Vor dem Parsen werden Twig-Kommentare ({# … #}) und {% verbatim %}-Abschnitte entfernt, Whitespace-Control ({%- … -%}) wird berücksichtigt. Ein auskommentierter oder wörtlich ausgegebener Block ist kein Override.
  • Nur Blocks auf Verschachtelungstiefe 0 gelten als Override. Blocks innerhalb eines überschriebenen Core-Blocks rendern sehr wohl und werden nicht gemeldet.
  • Dynamisch zusammengesetzte Include-Ziele ('.../partials/' ~ section ~ '.html.twig') werden übersprungen, weil es keinen einzelnen Pfad zu prüfen gibt.
  • ignore missing-Includes sind nie ein Fehler — das ist der dokumentierte Weg, ein optionales Template einer anderen Extension zu referenzieren. Löst der Pfad nirgends auf, gibt es aber eine Warnung.
  • Ein fremder Bundle-Namespace (@KlarnaPayment/...) wird übersprungen — der ist eindeutig eine andere Extension.
  • Ein @Storefront/...-Pfad, den Core nicht hat, ist nicht von „andere Extension liefert ihn" unterscheidbar. Der Check schlägt deshalb an; für den zweiten Fall gibt es .twig-check-ignore.

TwigConfigKeyDeclared

Findet config('<Extension>.config.x')-Aufrufe auf Keys, die src/Resources/config/config.xml nicht deklariert. Ein solcher Aufruf ist null, also ist jedes {% if %} darüber permanent falsch und das Feature stillschweigend aus. Passiert typischerweise, wenn ein Key in der config.xml umbenannt wurde, in den Templates aber nicht.

Nur Keys des eigenen Namespace werden geprüft. Keys von Shopware Core (core.loginRegistration.showSalutation) oder einer anderen Erweiterung (SwagPayPal.settings.clientId, KlarnaPayment) sind woanders deklariert und von hier aus nicht verifizierbar — sie werden per Konstruktion ignoriert, es braucht dafür keine Ausnahme:

{{ config('core.loginRegistration.showAccountTypeSelection') }}  {# ignoriert #}
{{ config('SwagPayPal.settings.clientId') }}                     {# ignoriert #}
{{ config('MyExtension.config.declaredKey') }}                   {# geprüft, ok #}
{{ config('MyExtension.config.renamedKey') }}                    {# geprüft -> Fehler #}

Der eigene Namespace wird aus der composer.json abgeleitet (extra.shopware-plugin-class-Basename, Fallback: erster autoload.psr-4-Key). Der Basename ist wichtig und nicht dasselbe wie der PSR-4-Prefix: bei LenzPlatformCreditManagement ist der Prefix Lenz\CreditManagement, das Config-Präfix aber LenzPlatformCreditManagement.

Ein Key des eigenen Namespace, den die config.xml absichtlich nicht deklariert — interner Zustand, der programmatisch geschrieben statt im Admin konfiguriert wird — wird als config-ok deklariert:

config-ok internalLastSync
config-ok internal*

Nicht als Config-Key gezählt werden <name>-Elemente unterhalb von <options><option>, das sind Option-Labels.

TwigOrphanTemplate

Findet Standalone-Templates, die offenbar nichts rendert. Ein Template ohne sw_extends ist nur erreichbar, wenn es irgendwo includiert wird oder wenn es einen Core-Pfad beschattet und daher von Core gerendert wird. Trifft keins von beidem zu, ist es meist übrig geblieben — ein Partial, dessen Include entfernt wurde, oder ein Rest aus einem Refactoring.

Warnung, kein Fehler: ein Partial kann legitim vom Theme des Shops oder von einer anderen Extension gerendert werden, und das ist von hier aus nicht erkennbar. Solche Fälle mit orphan-ok in .twig-check-ignore dauerhaft stummschalten.

Als Referenz zählen:

  • sw_include / include / embed aus den Templates der Extension
  • sw_extends aus den Templates der Extension
  • Template-Pfade als String in den PHP-Dateien der Extension — Dokument-Vorlagen, Exporte und Mails werden über einen Controller oder eine Twig-Umgebung gerendert, nicht über einen Include
  • ein gleichnamiger Core-Pfad (dann rendert Core das Template)

Dynamisch zusammengesetzte Includes werden berücksichtigt: bei '.../partials/' ~ section ~ '-box.html.twig' gilt nicht das ganze Verzeichnis als referenziert, sondern nur partials/*-box.html.twig. Sonst würde ein tatsächlich unbenutztes Geschwister-Template im gleichen Ordner maskiert.

PHP

DeclareStrictPhpCheck

Jede .php-Datei muss declare(strict_types=1); enthalten.

PhpShopware67Deprecations

Nur bei minimaler Shopware-Version 6.7. Meldet:

  • $qb->execute()->fetchAll()$qb->executeQuery()->fetchAllAssociative()

Administration

AdminJsShopware67Deprecations

Nur bei 6.7, nur .js unter /Resources/app/administration/:

  • Shopware.Context.api.currentLanguageIdShopware.Store.get('context').api.languageId
  • context/setApiLanguageIdShopware.Store.get('context').setApiLanguageId(...)
  • Shopware.ContextShopware.Store.get('context')
  • @import '~scss/variables.scss';@import '~scss/variables';

AdminTwigShopware67Deprecations

Nur bei 6.7, nur .html.twig unter /Resources/app/administration/. Prüft <mt-button>-Tags auf router-link / routerLink / routerlink und verlangt stattdessen @click="$router.push({ name: '...' })".

config.xml

ConfigXmlValue

Verlangt <defaultValue> statt <value> in src/Resources/config/config.xml. <value> wird von Shopware nicht als Standardwert übernommen, die Option bleibt also leer.

CHANGELOG

ChangelogStartNext

CHANGELOG*.md muss mit # NEXT beginnen.

ChangelogNotAllowedCharacters

Typografische Anführungszeichen , , sind in CHANGELOG*.md nicht erlaubt.

Assets

SvgActiveContentCheck

Meldet aktive Inhalte in .svg-Dateien, die Shopware nicht akzeptiert: <script>, Event-Handler-Attribute (onload=, onclick= …), javascript:-URIs, <foreignObject>, <!DOCTYPE>, <!ENTITY>, Processing Instructions außer der XML-Deklaration, sowie externe oder data:-href-Referenzen (erlaubt sind nur lokale Fragment-Referenzen #id).

Storefront-Templates finden

TwigTemplateOverride braucht die Core-Templates zum Vergleich. Gesucht wird in dieser Reihenfolge:

  1. $SW_STOREFRONT_VIEWS
  2. <extension>/vendor/shopware/storefront/Resources/views
  3. drei bzw. vier Ebenen über der Extension (Extension liegt in custom/plugins/<name> einer Installation)
  4. /opt/shopware/vendor/shopware/storefront/Resources/views
  5. /shopware/vendor/shopware/storefront/Resources/views

Wird nichts gefunden, erscheint eine sichtbare Meldung und die Twig-Prüfungen werden übersprungen — der Build schlägt nicht fehl. Ein stiller Durchlauf wäre schlimmer als ein Fehlschlag, deshalb die Meldung.

Drei Wege, die Templates bereitzustellen:

# a) im Pipeline-Step mitinstallieren - der empfohlene Weg, Nr. 2 der Suchliste
composer require --dev lenzebusiness/shopware-extension-check "dev-main" shopware/storefront "6.7.*"
# b) Pfad explizit setzen, z. B. wenn eine Shopware-Installation vorliegt
SW_STOREFRONT_VIEWS=/path/to/vendor/shopware/storefront/Resources/views \
  vendor/bin/shopware-extension-check .
// c) dauerhaft in der composer.json der Extension
"require-dev": {
    "shopware/storefront": ">=6.7.0.0 <6.8.0.0"
}

Variante a) hält die Extension-composer.json frei und steht direkt neben dem Aufruf, ist also beim Lesen der Pipeline nicht zu übersehen. Variante c) wirkt auch lokal, ohne dass man an den Pfad denken muss.

Wer die Extension innerhalb einer Shopware-Installation prüft (custom/plugins/<name>), braucht nichts davon — Punkt 3 der Suchliste greift.

.twig-check-ignore

Manches lässt sich lokal nicht verifizieren. Der Hauptfall: Templates, die eine andere Extension liefert. Die werden unter dem @Storefront-Namespace adressiert wie Core-Templates — „von Core entfernt" und „von anderer Extension geliefert" sind daher nicht unterscheidbar. Der Check schlägt konservativ an; die Ausnahme erklärst du in dieser Datei.

Die Datei liegt im Wurzelverzeichnis der geprüften Extension, direkt neben composer.json:

MyExtension/
├── composer.json
├── .twig-check-ignore
└── src/

Format

Eine Regel pro Zeile: Direktive, Whitespace, Pattern. # beginnt einen Kommentar. Patterns sind fnmatch-Globs, in denen * auch / matcht.

DirektivePattern bezieht sich aufWirkung
externalnamespacetes Template (@Storefront/...)Pfad wird von einer anderen Extension geliefert. Kein Befund bei sw_extends oder sw_include darauf; bei sw_extends werden zusätzlich die Blocks nicht geprüft, weil der Parent nicht lesbar ist.
ignorePfad relativ zu src/Resources/views/Template komplett von allen Twig-Prüfungen ausnehmen.
ignore<pfad>:<blockname>Nur diesen einen Block ausnehmen.
orphan-okPfad relativ zu src/Resources/views/Standalone-Template, das von außerhalb der Extension gerendert wird — vom Theme des Shops oder von einer anderen Extension. Schaltet die Warnung von TwigOrphanTemplate aus.
config-okConfig-Key-Name (ohne Namespace-Präfix)Key des eigenen Namespace, den die config.xml absichtlich nicht deklariert — interner Zustand statt Admin-Option. Nur für den eigenen Namespace nötig; Core- und Fremd-Keys werden ohnehin nicht geprüft.

Beispiel

# Exceptions for shopware-extension-check
#
# Jeder Eintrag braucht eine Begruendung. Eine undokumentierte Ausnahme ist der
# Weg, auf dem sich der naechste stille Fehler versteckt - wer nicht sagen kann,
# warum ein Pfad nicht pruefbar ist, hat wahrscheinlich keinen Sonderfall,
# sondern einen Bug.

# Die Klarna-Box auf der Confirm-Page. KlarnaPayment liefert diese Templates
# unter dem @Storefront-Namespace, sie fehlen also ohne diese Extension - genau
# deshalb benutzen die Includes `ignore missing`.
external @Storefront/storefront/klarna/*

# Wird vom Theme des Kunden gerendert, nicht von dieser Extension.
ignore storefront/component/custom/legacy-teaser.html.twig

# Der Block existiert nur im Theme XY, das dieses Template weiterreicht.
ignore storefront/page/product-detail/index.html.twig:xy_special_slot

# Cursor des naechtlichen Imports. Wird vom Command geschrieben, nicht im Admin
# gepflegt, hat also absichtlich keinen Eintrag in der config.xml.
config-ok importCursor

Hinweise

  • Eine unbekannte Direktive wird gemeldet, nicht ignoriert. Ein Tippfehler soll keine Prüfung stillschweigend abschalten.
  • Für einen fremden Bundle-Namespace (@KlarnaPayment/...) braucht es keine Regel — der wird ohnehin übersprungen. external ist nur für Pfade unter @Storefront nötig.
  • Für ein optionales Template ist {% sw_include ... ignore missing %} die bessere Lösung als eine external-Regel: das ist zur Laufzeit sicher und dokumentiert die Absicht direkt im Template.
  • Ohne die Datei verhält sich alles wie ohne Ausnahmen; sie ist optional.

Eine neue Prüfung schreiben

Neue Klasse in src/Inspection/Check/:

<?php

namespace LenzEbusiness\ShopwareExtensionCheck\Inspection\Check;

use LenzEbusiness\ShopwareExtensionCheck\Inspection\AbstractInspection;
use LenzEbusiness\ShopwareExtensionCheck\Inspection\InspectionDataBag;
use LenzEbusiness\ShopwareExtensionCheck\Inspection\InspectionResult;

class MyCheck extends AbstractInspection
{
    public function isSupported(InspectionDataBag $inspectionDataBag): bool
    {
        return str_ends_with($inspectionDataBag->getFileName(), '.php');
    }

    public function inspect(InspectionDataBag $inspectionDataBag): InspectionResult
    {
        $inspectionResult = new InspectionResult();

        $content = $this->getFileContent($inspectionDataBag->getFilePath());

        if (str_contains($content, 'foo')) {
            $inspectionResult->addError('Do not use foo, use bar instead.');
        }

        if (str_contains($content, 'baz')) {
            // Does not fail the build.
            $inspectionResult->addWarning('baz is discouraged, consider bar.');
        }

        return $inspectionResult;
    }
}

Es braucht keine Registrierung — der Ordner wird zur Laufzeit gescannt.

Regeln:

  • Nur Inspections in src/Inspection/Check/. Der Scanner instanziiert jede Datei in diesem Ordner als Inspection. Hilfsklassen gehören woanders hin, z. B. nach src/Twig/ (siehe TwigContext).
  • Nichts auf eine konkrete Extension hartcodieren — Namespaces und Pfade aus der composer.json der geprüften Extension ableiten.
  • Schweregrad bewusst wählen. addError() bricht den Build und muss sicher sein. Ist ein Befund von innen nicht entscheidbar — weil das Theme des Shops oder eine andere Extension die Antwort kennt — dann addWarning().
  • Fehlermeldungen sagen, was zu tun ist, nicht nur, was falsch ist. Sie landen in einer Pipeline-Ausgabe, ohne den Code daneben.
  • Negativ testen. Eine Prüfung, die nie anschlägt, ist schlimmer als keine — sie erzeugt Vertrauen, das sie nicht verdient. Also gegen eine absichtlich kaputte Datei laufen lassen und den Befund sehen.

Für teure Schritte (Verzeichnisse ablaufen, XML parsen, Core-Templates lesen) static-Caches benutzen, da pro Datei neu instanziiert wird.

Tests

php tests/run.php                    # alle Fixtures und Invarianten
php tests/run.php dead-block         # nur eine Fixture
php tests/run.php directory-argument # nur eine Invariante

Ein Name, der weder Fixture noch Invariante ist, bricht mit Exit 1 ab. Ein gefilterter Lauf, der nichts getroffen hat, hat nichts geprüft und darf nicht grün aussehen.

Jede Fixture unter tests/Fixtures/<name>/ ist eine minimale Extension, die genau einen Sachverhalt verkörpert. Die Erwartung liegt außerhalb der Fixture in tests/expected/<name>.php — sonst würde DeclareStrictPhpCheck auf dem Test-Gerüst selbst anschlagen. Sie prüft strukturierte Daten (Anzahl Errors und Warnungen, betroffene Dateien, Stichworte) statt wörtlicher Meldungstexte; sonst invalidiert jede Umformulierung alle Erwartungen.

Als Core-Templates dient der Stub in tests/Fixtures/_storefront/ und nicht das echte shopware/storefront. Die Suite soll beantworten „verhält sich der Checker richtig", nicht „wie sieht Shopware 6.7.11 gerade aus" — mit den echten Templates würde sie bei jedem Shopware-Update aus dem falschen Grund rot. Ob Core einen Block tatsächlich umbenannt hat, beantwortet der Lauf gegen echte Extensions.

Warum es Paare gibt

Die Fixtures kommen in zwei Sorten, und die zweite ist die wichtigere:

  • dead-block, missing-include, undeclared-config-key … — muss gemeldet werden
  • valid-nested-block, valid-whitespace-control, valid-verbatim … — darf nicht gemeldet werden

Eine valid-*-Fixture allein reicht aber nicht: sie erwartet „nichts gemeldet" und besteht damit auch dann, wenn die Prüfung überhaupt nicht läuft. Genau das trat auf — extendedTemplate() erkannte {%- sw_extends -%} nicht, die Override-Prüfung wurde stumm übersprungen, und valid-whitespace-control blieb grün.

Darum hat jede Parsing-Variante ein Gegenstück detects-*, das dieselbe Syntax benutzt und trotzdem einen echten Fehler enthält. Nur beide zusammen decken die Variante ab.

Invarianten ohne Fixture

Vier Eigenschaften lassen sich nicht als Fixture ausdrücken, weil sie das Verhalten des Checkers selbst betreffen und nicht seinen Befund. Der Runner prüft sie zusätzlich:

  • discovery-guard — eine ungültige Datei in Check/ (Trait, abstrakte Klasse, Datei ohne Klasse) wird übersprungen statt zu fatalen. Die Testdaten dafür liegen in tests/Discovery/.
  • repeated-run-does-not-accumulate — zweimal check() auf derselben Instanz meldet nicht doppelt.
  • two-extensions-stay-isolated — zwei Extensions im selben Prozess sehen die Caches der anderen nicht.
  • directory-argument — das Verzeichnis-Argument des Einsprungpunkts, absolut wie relativ. Nicht als Fixture abbildbar, weil bin/ einen Composer-Autoloader braucht, den es im Repo ohne Install nicht gibt; getestet wird deshalb ExtensionDirectory::resolve().

Eine neue Prüfung testen

  1. Fixture-Verzeichnis mit composer.json, src/Resources/config/config.xml und den Dateien unter Test anlegen.
  2. tests/expected/<name>.php schreiben.
  3. Den Fix testweise zurücknehmen und prüfen, dass die Fixture rot wird. Ein Test, der nie rot war, ist nichts wert. Alle Prüfungen hier sind so verifiziert — und in drei Fällen war die Fixture dabei aus dem falschen Grund grün.

Bekannte Einschränkungen

  • Keine Zeilennummern. Befunde werden pro Datei gemeldet, nicht pro Zeile.
  • Twig-Prüfungen brauchen die Core-Templates. Ohne sie überspringen sie sich mit einer Meldung, siehe Storefront-Templates finden. In der Pipeline gehört shopware/storefront deshalb in die composer require-Zeile.
  • Nur der @Storefront-Namespace wird verifiziert. Erweitert eine Extension ein Template einer anderen Extension über deren eigenen Namespace (@FooPlugin/...), wird nichts geprüft — die Templates liegen nicht vor.
  • Config-Keys werden nur in Twig geprüft. Ein SystemConfigService::get() auf einen nicht deklarierten Key in PHP fällt nicht auf, obwohl es dieselbe stille Wirkung hat (null). Eine Erweiterung darauf wäre unkritisch: die Beschränkung auf den eigenen Namespace gilt dort genauso.
  • Twig wird per Regex gelesen, nicht geparst. Kommentare, {% verbatim %} und Whitespace-Control sind behandelt; ein {% block %} innerhalb eines String-Literals wäre es nicht. In der Praxis nicht aufgetreten.
  • Kein JSON-Output. Der Report ist für Menschen. Die Befunde liegen aber strukturiert vor (getErrors(), getWarnings(), Finding::$line), eine maschinenlesbare Ausgabe wäre also eine kleine Ergänzung.