CI/CD
.yml
GitLab · PHP · Qualitätssicherung · Sicherheit
PHPUnit, PHPStan, PHPCS und Sicherheitschecks
in GitLab CI bündeln

Code, der nicht getestet und nicht analysiert wird, akkumuliert stille Fehler. PHPUnit, PHPStan, PHPCS und composer audit als parallele GitLab-CI-Jobs gebündelt liefern nach jedem Commit Rückmeldung über Tests, Typsicherheit, Stil und bekannte Sicherheitslücken – automatisch und ohne manuellen Aufwand.

14 Min. Lesezeit PHPUnit · PHPStan Level 8 · PHPCS · composer audit Magento 2.4 · PHP 8.4 · GitLab CI/CD

1. Warum alle Checks in die Pipeline gehören

Code-Qualitätswerkzeuge, die nur lokal und nur von gewissenhaften Entwicklern ausgeführt werden, sind keine Qualitätssicherung – sie sind eine Hoffnung. PHPUnit, PHPStan, PHPCS und composer audit haben nur dann Wert als Prozess-Absicherung, wenn sie automatisch bei jedem Commit und jedem Merge Request ausgeführt werden. Ohne diese Automatisierung entsteht unvermeidlich Drift: ein Entwickler läuft PHPStan nicht, ein anderer vergisst den Code-Style, und das Ergebnis ist eine Codebasis, die mit steigendem Druck immer inkonsistenter wird.

GitLab CI ist der richtige Ort für diese Automatisierung. Die Test-Jobs laufen parallel, liefern Ergebnisse in GitLab-Merge-Requests als Pass/Fail-Marker und verhindern, dass Code, der Checks nicht besteht, auf den main-Branch gemergt werden kann. Das kostet initial Konfigurationsaufwand, zahlt sich aber in jeder Codebase aus, die länger als ein Quartal aktiv entwickelt wird. Besonders in Magento-Projekten mit intensiver Modul-Entwicklung ist diese Absicherung kein Luxus, sondern Betriebshygiene.

2. PHPUnit in GitLab CI integrieren

PHPUnit in GitLab CI zu betreiben ist konzeptuell einfach, erfordert in Magento-Projekten aber einige Vorkehrungen. Magento's Test-Framework ist auf Integration Tests ausgelegt, die eine laufende Datenbank und Redis-Instanz benötigen. Unit Tests hingegen sind vollständig isoliert und laufen ohne externe Abhängigkeiten – sie sind der erste und wichtigste Test-Typ für selbst geschriebene Module und ViewModels. Die Empfehlung: Unit Tests als eigenen Job in der Test-Stage ausführen, Integration Tests nur auf Staging oder in dafür vorbereiteten Services-Jobs.

GitLab bietet mit services in der Pipeline-Konfiguration die Möglichkeit, MySQL und Redis als Seicar-Container neben dem Test-Job zu starten. Das ermöglicht auch Integration Tests direkt in der Pipeline. Für Magento-Integration-Tests ist zusätzlich eine install-config-files.php nötig, die die Testdatenbank konfiguriert. Diese Komplexität ist für die meisten Projekte zu hoch – Unit Tests mit vollständiger Isolation sind der pragmatischere Ausgangspunkt.

# .gitlab-ci.yml — parallel quality assurance jobs in test stage
test:phpunit:
  stage: test
  image: php:8.4-cli
  variables:
    COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
  cache:
    key:
      files: [composer.lock]
    paths: [.cache/composer/, vendor/]
    policy: pull
  before_script:
    - apt-get update -qq &&
        apt-get install -y -qq git unzip libzip-dev
    - docker-php-ext-install zip pdo_mysql
    - composer install --no-dev --prefer-dist --no-interaction
    # Install test dependencies separately
    - composer require --dev
        phpunit/phpunit:^11
        --no-interaction --no-update
    - composer update phpunit/phpunit --no-interaction
  script:
    - ./vendor/bin/phpunit
        --testsuite Unit
        --log-junit junit-unit.xml
        --coverage-text
  artifacts:
    when: always
    reports:
      junit: junit-unit.xml
    expire_in: 1 week
  only:
    - merge_requests
    - main
    - tags

test:phpstan:
  stage: test
  image: php:8.4-cli
  cache:
    key:
      files: [composer.lock]
    paths: [.cache/composer/, vendor/]
    policy: pull
  script:
    # Run PHPStan at level 6 for Magento modules
    - ./vendor/bin/phpstan analyse
        app/code/
        --level=6
        --error-format=gitlab
        --no-progress
        > phpstan-report.json || true
    - ./vendor/bin/phpstan analyse
        app/code/
        --level=6
        --no-progress
  artifacts:
    when: always
    paths: [phpstan-report.json]
    expire_in: 1 week
  only:
    - merge_requests
    - main

3. PHPStan: statische Analyse im Pipeline-Job

PHPStan findet Fehler, die Tests nicht finden: falsche Typen, nicht existierende Methoden, undefinierte Eigenschaften und logische Inkonsistenzen, die erst zur Laufzeit explodieren. Für Magento-Projekte ist PHPStan auf Level 6–8 sinnvoll, je nach Reife der Codebasis. Level 0 prüft nur offensichtliche Fehler, Level 8 erzwingt vollständige Typ-Annotationen. Die pragmatische Strategie: mit Level 4 beginnen, alle bestehenden Fehler mit einer Baseline ausblenden, und dann bei jeder Änderung sicherstellen, dass keine neuen Fehler entstehen.

Für Magento ist das PHPStan-Plugin bitExpert/phpstan-magento unerlässlich: es kennt Magento-spezifische Patterns wie ObjectManager::get(), Factory-Klassen und die Proxy-Generierung und verhindert tausende Falsch-Positive. Ohne dieses Plugin erzeugt PHPStan in jedem Magento-Projekt so viele False-Positives, dass das Tool nicht sinnvoll nutzbar ist. Die Konfiguration gehört in eine phpstan.neon-Datei im Projektroot, die vom GitLab-CI-Job automatisch aufgenommen wird.

4. PHPCS und Coding-Standard-Checks

PHPCS (PHP_CodeSniffer) prüft, ob der Code dem definierten Coding-Standard entspricht. Für Magento-Projekte ist das Magento2-Standard aus dem Paket magento/magento-coding-standard der richtige Ausgangspunkt. Dieser Standard umfasst PSR-12, Magento-spezifische Regeln für Kommentare, String-Interpolation, Datenbankabfragen und die Verwendung von Dependeny-Injection statt ObjectManager. In GitLab CI läuft PHPCS als eigenständiger Job parallel zu PHPStan und PHPUnit.

Wichtig: PHPCS als Pipeline-Blocker ist sinnvoll, aber erst wenn die gesamte Codebasis konform ist. Wer PHPCS in eine bestehende Codebasis einführt, nutzt besser --report=diff und phpcbf zum automatischen Korrigieren, bevor er die Pipeline-Checks aktiviert. Der GitLab-CI-Job für PHPCS lässt sich mit allow_failure: true zunächst als Warnung konfigurieren und später auf allow_failure: false umstellen, wenn die Codebasis bereinigt ist.

5. Sicherheitschecks: composer audit und mehr

Sicherheitschecks in der Pipeline sind kein Nice-to-have, sondern für jeden produktiven E-Commerce-Shop Pflicht. Der erste und einfachste Check ist composer audit: Composer 2.4+ hat diesen Befehl eingebaut und prüft alle installierten Pakete gegen die Sicherheitsdatenbank von packagist.org. Wenn eine bekannte CVE für eine installierte Version vorliegt, schlägt der Befehl mit Exit-Code 1 fehl – was den Pipeline-Job zum Scheitern bringt und das Team informiert.

Für tiefgehendere Sicherheitsanalysen eignet sich local-php-security-checker, das ebenfalls die Symfony-Security-Datenbank abfragt und im JSON-Format ausgibt. GitLab Ultimate bietet darüber hinaus eingebaute SAST (Static Application Security Testing) und Dependency Scanning als fertige Template-Jobs. Für Projekte ohne GitLab Ultimate ist die Kombination aus composer audit und einem manuell konfigurierten local-php-security-checker-Job ausreichend und kostenlos.

# Security and code style jobs running in parallel
test:phpcs:
  stage: test
  image: php:8.4-cli
  cache:
    key:
      files: [composer.lock]
    paths: [.cache/composer/, vendor/]
    policy: pull
  script:
    # Run PHPCS with Magento2 standard on custom code only
    - ./vendor/bin/phpcs
        --standard=Magento2
        --extensions=php
        --ignore=*/vendor/*,*/generated/*
        app/code/
  allow_failure: false
  only:
    - merge_requests
    - main

test:security:
  stage: test
  image: php:8.4-cli
  cache:
    key:
      files: [composer.lock]
    paths: [.cache/composer/]
    policy: pull
  script:
    # Audit all installed packages for known CVEs
    - composer audit --no-dev --format=json > security-audit.json
        || (cat security-audit.json && exit 1)
    # Additional: check for abandoned packages
    - composer outdated --no-dev --format=json |
        php -r "
          \$data = json_decode(file_get_contents('php://stdin'), true);
          \$abandoned = array_filter(\$data['installed'] ?? [],
            fn(\$p) => \$p['abandoned'] ?? false);
          foreach(\$abandoned as \$p) {
            echo 'ABANDONED: ' . \$p['name'] . PHP_EOL;
          }"
  artifacts:
    when: always
    paths: [security-audit.json]
    expire_in: 1 month
  only:
    - merge_requests
    - main
    - schedules

6. Parallele Ausführung aller Test-Jobs

Der entscheidende Performance-Vorteil einer sauber konfigurierten Test-Stage ist die parallele Ausführung. PHPUnit, PHPStan, PHPCS und composer audit haben keine Abhängigkeiten untereinander – sie können alle gleichzeitig auf verschiedenen GitLab-Runnern ausgeführt werden. Wenn jeder Job einzeln zwei Minuten dauert, läuft die gesamte Test-Stage bei paralleler Ausführung trotzdem in zwei Minuten – nicht in acht. Das macht den Unterschied zwischen einer Pipeline, die das Team akzeptiert, und einer, die als zu langsam übersprungen wird.

Für die parallele Ausführung müssen alle Jobs in derselben Stage definiert sein und unabhängige Caches verwenden. Der Build-Job in der vorherigen Stage produziert das vendor/-Verzeichnis als Artefakt oder via Cache; alle Test-Jobs nehmen diesen Cache mit policy: pull (nur lesen, nicht schreiben) ab. So wird der Cache nicht durch parallele Schreibzugriffe korrumpiert, und alle Test-Jobs starten gleichzeitig, sobald die Build-Stage abgeschlossen ist.

7. Test-Berichte in GitLab sichtbar machen

GitLab bietet native Unterstützung für JUnit-XML-Berichte: wenn ein Test-Job artifacts.reports.junit definiert, erscheinen die Test-Ergebnisse direkt im Merge-Request als übersichtliche Tabelle. Jeder fehlgeschlagene Test wird mit seiner Fehlermeldung angezeigt, ohne dass der Reviewer in die Job-Logs schauen muss. PHPUnit generiert diese Berichte mit dem Flag --log-junit junit.xml; PHPStan kann mit --error-format=junit ebenfalls JUnit-XML ausgeben.

Für Code-Coverage-Berichte bietet GitLab ab der Coverage-Konfiguration in den Projekt-Einstellungen: mit einem Regex-Pattern wie /Lines:\s+(\d+\.\d+)%/ extrahiert GitLab den Coverage-Wert aus dem PHPUnit-Text-Report und zeigt ihn als Badge im Repository an. Das motiviert Teams, den Coverage-Wert über die Zeit zu steigern, ohne dass ein externes Tool wie Coveralls oder Codecov nötig wäre.

8. QS-Werkzeuge im Vergleich

Die vier zentralen Qualitätswerkzeuge decken unterschiedliche Fehlerklassen ab und ergänzen sich gegenseitig. Keines kann das andere vollständig ersetzen.

Werkzeug Fehlerklasse Magento-Plugin nötig? GitLab-Report-Format
PHPUnit Laufzeit-Fehler, Logik-Bugs, Regressionen Nein JUnit XML
PHPStan Typfehler, undefinierte Methoden, Logikfehler Ja (bitExpert/phpstan-magento) JUnit XML / GitLab JSON
PHPCS Stil-Verletzungen, Standard-Abweichungen Ja (magento/magento-coding-standard) Text / Checkstyle XML
composer audit Bekannte CVEs in Abhängigkeiten Nein JSON / Text
GitLab SAST (Ultimate) Sicherheitslücken im eigenen Code Nein (eingebaut) GitLab Security Dashboard

9. Magento-spezifische Besonderheiten

Magento-Projekte haben spezifische Anforderungen, die Standard-PHP-QS-Konfigurationen nicht abdecken. Die wichtigste ist die Unterscheidung zwischen eigenem Code (app/code/) und Magento-Core- und Drittanbieter-Code (vendor/). PHPStan, PHPCS und PHPUnit sollen ausschließlich den eigenen Code prüfen. Falsch konfigurierte Tools, die auch vendor/ analysieren, erzeugen tausende irrelevante Fehler und machen die Ausgabe unbrauchbar.

Ein zweiter Magento-spezifischer Aspekt ist die Generated-Code-Abhängigkeit. Viele Magento-Klassen – Factories, Proxies, Interceptors – existieren erst nach setup:di:compile. PHPStan benötigt diese generierten Klassen, um korrekte Typ-Analysen durchzuführen. Das bedeutet: der PHPStan-Job muss entweder den generierten Code als Artefakt vom Build-Job erhalten oder selbst ein setup:di:compile mit einer Stub-Datenbank ausführen. Die einfachere Lösung ist es, dem PHPStan-Job die generated/-Artefakte aus dem Build-Stage zur Verfügung zu stellen.

10. Zusammenfassung

PHPUnit, PHPStan, PHPCS und composer audit in GitLab CI zu bündeln ist keine Einzel-Maßnahme, sondern ein System. Jedes Werkzeug deckt eine andere Fehlerklasse ab, und nur in Kombination bieten sie eine belastbare Qualitätssicherung. Die parallele Ausführung in der Test-Stage hält die Pipeline schnell; das JUnit-Reporting macht Fehler direkt im Merge-Request sichtbar; die Magento-spezifischen Plugins verhindern False-Positives. Das Ergebnis ist ein Prozess, der schlechten Code systematisch verhindert, statt ihn in Code-Reviews zu jagen.

Der pragmatische Startpunkt für bestehende Projekte: zuerst composer audit aktivieren (sofortiger Mehrwert, null Fehlalarme), dann PHPCS mit allow_failure: true einführen und die Codebasis schrittweise bereinigen, dann PHPStan mit Baseline und schrittweise steigendem Level, schließlich PHPUnit für eigene Module. Dieser schrittweise Ansatz verhindert, dass das Team von hundert Fehlern erschlagen wird und die QS-Jobs deaktiviert.

PHP-Qualitätssicherung in GitLab CI — Das Wichtigste auf einen Blick

Parallel ausführen

PHPUnit, PHPStan, PHPCS und composer audit in derselben Test-Stage – alle parallel, unabhängig, in unter 3 Minuten.

Nur eigenen Code prüfen

app/code/ analysieren, vendor/ und generated/ explizit ausschließen – sonst tausende Fehlalarme.

Magento-Plugins

bitExpert/phpstan-magento und magento/magento-coding-standard sind Pflicht für sinnvolle Analysen.

Schrittweise einführen

composer audit zuerst, dann PHPCS, PHPStan mit Baseline, zuletzt PHPUnit – Fehler nie auf einmal.

11. FAQ: PHPUnit, PHPStan, PHPCS und Sicherheitschecks in GitLab CI

1Welche QS-Werkzeuge sind für Magento am wichtigsten?
composer audit (Sicherheit), PHPStan + bitExpert-Plugin (Typfehler), PHPCS + Magento-Standard (Stil), PHPUnit für eigene Module.
2Wie verhindere ich PHPStan-False-Positives in Magento?
Plugin bitExpert/phpstan-magento kennt Factories, Proxies und ObjectManager. Ohne dieses Plugin ist PHPStan in Magento nicht nutzbar.
3Wie führe ich alle Test-Jobs parallel aus?
Alle in derselben Stage, Cache mit policy: pull teilen. GitLab startet alle Jobs einer Stage gleichzeitig auf verfügbaren Runnern.
4Was prüft composer audit?
Alle installierten Pakete gegen packagist.org-Sicherheitsdatenbank. Bekannte CVEs → Exit-Code 1 → Pipeline schlägt fehl.
5PHPUnit-Ergebnisse im Merge Request anzeigen?
--log-junit junit.xml + artifacts.reports.junit: junit.xml im Job – GitLab zeigt Ergebnisse direkt im Merge-Request.
6Mit welchem PHPStan-Level beginnen?
Level 4–6 als Start, Baseline für bestehende Fehler erstellen, Level schrittweise erhöhen. Nicht mit Level 8 beginnen.
7Muss PHPCS immer die Pipeline blockieren?
Nein. allow_failure: true bei Einführung, mit phpcbf bereinigen, dann auf allow_failure: false umstellen.
8Welchen PHPCS-Standard für Magento?
magento/magento-coding-standard: PSR-12 plus Magento-spezifische Regeln für DI, String-Interpolation und Datenbankabfragen.
9PHPStan mit generiertem Magento-Code?
generated/-Ordner als Artefakt vom Build-Job im PHPStan-Job verfügbar machen. Factories und Proxies sind für korrekte Typ-Analysen nötig.
10Wie oft Sicherheitschecks ausführen?
Bei jedem Merge Request und täglich via Scheduled Pipelines. Neue CVEs entstehen täglich – nur regelmäßiger Scan erkennt sie rechtzeitig.