Tools statt Diskussionen im Review
Wer PSR-12 durchsetzen will, ohne jeden Pull Request mit Kommentaren zu Einrückung und Klammernposition zu blockieren, braucht Automatisierung statt guten Willens. PHP_CodeSniffer und PHP-CS-Fixer, kombiniert mit Pre-Commit-Hooks und CI-Pipeline, beenden Formatierungsdiskussionen im Review dauerhaft, auch in gewachsenen Legacy-Projekten.
Inhaltsverzeichnis
- 1. Warum Code-Style-Diskussionen im Review Zeit kosten
- 2. PHP_CodeSniffer: PSR-12 als Ruleset durchsetzen
- 3. PHP-CS-Fixer: Automatisches Umformatieren statt nur Melden
- 4. Pre-Commit-Hooks: Verstöße vor dem Commit blockieren
- 5. CI-Integration: Der Build als letzte Instanz
- 6. Editor-Integration: Format-on-Save in PhpStorm und VS Code
- 7. Rollout auf Legacy-Code ohne Big-Bang-Reformat
- 8. Was danach im Review noch übrig bleibt
- 9. PHP_CodeSniffer vs. PHP-CS-Fixer im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Code-Style-Diskussionen im Review Zeit kosten
In vielen Teams verschlingt ein erheblicher Teil der Review-Kommentare nicht die Logik einer Änderung, sondern ihre Formatierung: fehlende Leerzeile vor einem return, falsche Einrückung nach einem Merge, eine öffnende Klammer in der falschen Zeile. Diese Kommentare sind fachlich meist unstrittig, sie kosten aber Zeit, erzeugen Reibung zwischen Autor und Reviewer, und verzögern den eigentlichen inhaltlichen Review um mehrere Iterationsrunden. Wer PSR-12 durchsetzen will, sollte diese Kategorie von Kommentaren komplett aus dem menschlichen Review entfernen, nicht durch mehr Disziplin, sondern durch Werkzeuge, die den Zustand objektiv und automatisch prüfen.
PSR-12 selbst ist eine Erweiterung des älteren PSR-2-Standards der PHP-FIG und deckt Details wie Einrückung mit vier Leerzeichen, Zeilenlänge, Positionierung von geschweiften Klammern, Leerzeilen zwischen Methoden und die korrekte Reihenfolge von Sichtbarkeitsmodifikatoren ab. Der Standard selbst ist unstrittig und breit akzeptiert, das eigentliche Problem liegt fast nie im Standard, sondern in seiner konsequenten, werkzeuggestützten Durchsetzung über die gesamte Codebasis und alle Teammitglieder hinweg.
Die folgenden Abschnitte zeigen, wie ein Team PSR-12 durchsetzen kann, von der reinen Erkennung über automatisches Auto-Fixing bis zur Absicherung in Pre-Commit-Hooks, CI-Pipeline und Editor, und wie sich das auch auf einer historisch gewachsenen Codebasis einführen lässt, ohne das Team mit einem einzigen riesigen Reformat-Commit zu überfordern.
2. PHP_CodeSniffer: PSR-12 als Ruleset durchsetzen
PHP_CodeSniffer, meist über den Composer-Befehl composer require --dev squizlabs/php_codesniffer installiert, prüft Code gegen ein deklaratives Ruleset und meldet jede Abweichung mit Datei, Zeile und einer eindeutigen Sniff-Kennung. Der eingebaute Standard PSR12 deckt bereits den vollständigen offiziellen Standard ab, ein eigenes phpcs.xml im Projekt-Root erweitert oder verfeinert diesen Standard um projektspezifische Ausnahmen, etwa für generierte Dateien oder Legacy-Verzeichnisse.
<?xml version="1.0"?>
<ruleset name="ProjectPSR12">
<description>PSR-12 with project-specific exclusions</description>
<rule ref="PSR12"/>
<file>src</file>
<file>tests</file>
<exclude-pattern>*/var/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>src/Legacy/*</exclude-pattern>
<arg name="colors"/>
<arg value="p"/>
<arg name="extensions" value="php"/>
</ruleset>
Der Aufruf vendor/bin/phpcs analysiert den konfigurierten Scope und listet jeden Verstoß mit exaktem Fundort auf, praktisch für einen ersten Überblick über den Umfang eines Rollouts. Wer PSR-12 durchsetzen will, ohne jede Regel manuell zu konfigurieren, profitiert davon, dass PSR12 als Standard bereits sämtliche relevanten Sniffs bündelt, einzelne Sniffs lassen sich bei Bedarf über <exclude>-Elemente gezielt deaktivieren, etwa wenn eine Regel mit einer projektspezifischen Konvention kollidiert.
Ein wichtiger Unterschied zu vielen anderen Linting-Tools: PHP_CodeSniffer meldet Verstöße primär, es korrigiert sie standardmäßig nicht. Für automatisches Fixing gibt es das mitgelieferte Schwesterwerkzeug phpcbf (PHP Code Beautifier and Fixer), das denselben Ruleset-Mechanismus nutzt, aber Dateien direkt überschreibt, sofern der jeweilige Sniff eine automatische Korrektur unterstützt. Nicht jeder Sniff ist automatisch fixbar, komplexere strukturelle Verstöße bleiben manuelle Aufgabe.
3. PHP-CS-Fixer: Automatisches Umformatieren statt nur Melden
PHP-CS-Fixer verfolgt von Anfang an einen anderen Ansatz als PHP_CodeSniffer: Es ist primär ein Auto-Fixer, der Code direkt umschreibt, das reine Melden ohne Korrektur ist eher die Ausnahme. Die Konfiguration erfolgt über eine PHP-Datei .php-cs-fixer.php im Projekt-Root, die ein Config-Objekt mit einem Set aktivierter Regeln zurückgibt, entweder als benanntes Regel-Set wie @PSR12 oder als feingranulare Liste einzelner Regeln.
<?php
declare(strict_types=1);
$finder = (new PhpCsFixer\Finder())
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
->exclude('Legacy');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'declare_strict_types' => true,
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'single_quote' => true,
'trailing_comma_in_multiline' => true,
])
->setFinder($finder)
->setRiskyAllowed(true);
Der Aufruf vendor/bin/php-cs-fixer fix überschreibt betroffene Dateien direkt, vendor/bin/php-cs-fixer fix --dry-run --diff zeigt stattdessen nur einen Vorschau-Diff, ohne etwas zu verändern, praktisch für CI-Checks, die einen Build fehlschlagen lassen sollen, ohne Dateien zu modifizieren. Das Flag --risky-allowed aktiviert Regeln, die das Verhalten des Codes theoretisch verändern könnten, etwa den Wechsel von array() zu [], in der Praxis für PSR-12-Konformität aber meist unbedenklich.
Der Unterschied zwischen phpcbf und php-cs-fixer fix liegt vor allem im Umfang des automatisch Korrigierbaren: PHP-CS-Fixer deckt deutlich mehr Regeln mit automatischer Korrektur ab, weil Auto-Fixing von Anfang an das Kernkonzept des Tools ist, während es bei PHP_CodeSniffer eine nachträgliche Ergänzung ist. Viele Teams kombinieren deshalb beide Tools: PHP_CodeSniffer für die Erkennung mit einem breiteren Sniff-Katalog inklusive nicht automatisch fixbarer Architektur-Regeln, PHP-CS-Fixer für das automatische Umformatieren des Großteils der reinen Stilverstöße.
4. Pre-Commit-Hooks: Verstöße vor dem Commit blockieren
Ein Pre-Commit-Hook verhindert, dass nicht konformer Code überhaupt erst ins Repository gelangt, statt ihn erst im Pull Request oder in der CI-Pipeline zu entdecken. Der entscheidende Vorteil gegenüber reiner CI-Prüfung: Der Entwickler bekommt sofortiges Feedback, lokal, bevor ein Commit überhaupt existiert, und die Korrektur passiert im selben Arbeitsschritt statt in einem separaten Fix-up-Commit später.
#!/usr/bin/env bash
# .git/hooks/pre-commit (or managed via a tool like Husky/Captain Hook)
set -euo pipefail
# Only check staged PHP files, not the entire codebase
staged_files=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')
if [ -z "$staged_files" ]; then
exit 0
fi
echo "Running PHP-CS-Fixer on staged files..."
vendor/bin/php-cs-fixer fix --dry-run --diff $staged_files
if [ $? -ne 0 ]; then
echo "PSR-12 violations found. Run 'vendor/bin/php-cs-fixer fix' and re-stage."
exit 1
fi
echo "Running PHP_CodeSniffer on staged files..."
vendor/bin/phpcs $staged_files
Der Hook beschränkt die Prüfung bewusst auf gestagte Dateien, statt jedes Mal die gesamte Codebasis zu scannen, das hält die Laufzeit im Sekundenbereich und macht den Hook praktikabel für den täglichen Gebrauch. Wer PSR-12 durchsetzen will, ohne Entwickler mit langen Wartezeiten bei jedem Commit zu frustrieren, sollte diesen Scope bewusst eng halten und die vollständige Codebasis stattdessen in CI prüfen.
Für Teams mit mehreren Sprachen oder komplexeren Hook-Anforderungen bieten sich Tools wie Captain Hook oder Grumphp an, beide PHP-native Alternativen zu generischen Lösungen wie Husky, die Composer-Skripte direkt als Git-Hooks registrieren und über eine deklarative Konfigurationsdatei verwaltet werden, statt rohe Shell-Skripte manuell zu pflegen.
5. CI-Integration: Der Build als letzte Instanz
Pre-Commit-Hooks lassen sich mit git commit --no-verify umgehen, absichtlich oder aus Versehen, weshalb die CI-Pipeline die eigentliche, nicht umgehbare Durchsetzungsinstanz bleibt. Ein CI-Job, der bei jedem Push und jedem Pull Request läuft, stellt sicher, dass kein nicht konformer Code jemals den Hauptbranch erreicht, unabhängig davon, ob der lokale Hook aktiv war oder nicht.
# .github/workflows/code-style.yml
name: Code Style
on: [push, pull_request]
jobs:
phpcs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: composer install --prefer-dist --no-progress
- name: Check PSR-12 compliance
run: vendor/bin/phpcs --report=checkstyle
- name: Check PHP-CS-Fixer rules
run: vendor/bin/php-cs-fixer fix --dry-run --diff
Das Flag --report=checkstyle gibt Ergebnisse im Checkstyle-XML-Format aus, das viele CI-Systeme und Code-Review-Tools direkt als Inline-Kommentare im Diff darstellen können, statt nur eine rohe Konsolenausgabe zu liefern. Wer PSR-12 durchsetzen als harte CI-Gate umsetzt, sollte den Job unbedingt als Required Check im Pull-Request-Workflow markieren, damit ein Merge ohne bestandenen Style-Check technisch gar nicht möglich ist, statt nur eine unverbindliche Warnung anzuzeigen.
Für schnellere Feedback-Zyklen lohnt sich ein separater, schneller Job ausschließlich für geänderte Dateien im Pull Request, kombiniert mit einem vollständigen, aber selteneren Scan der gesamten Codebasis, etwa nächtlich, um schleichende Abweichungen durch fehlerhafte Konfigurationsänderungen frühzeitig zu erkennen.
6. Editor-Integration: Format-on-Save in PhpStorm und VS Code
Die wirksamste Automatisierung ist die, die ein Entwickler nie bewusst wahrnimmt: Format-on-Save formatiert Code automatisch beim Speichern, sodass ein Entwickler PSR-12-Konformität praktisch nie manuell herstellen muss. PhpStorm unterstützt PHP-CS-Fixer nativ über eine Einstellung unter Settings → PHP → Quality Tools → PHP-CS-Fixer, dort wird der Pfad zur ausführbaren Datei und zur .php-cs-fixer.php-Konfiguration hinterlegt, danach lässt sich "Run on save" für den aktuellen Dateibereich aktivieren.
In VS Code übernimmt die Erweiterung junstyle.php-cs-fixer dieselbe Aufgabe, konfiguriert über editor.formatOnSave: true in Kombination mit "[php]": { "editor.defaultFormatter": "junstyle.php-cs-fixer" } in den Workspace-Settings. Wichtig für Teamkonsistenz: Diese Editor-Einstellungen sollten projektbezogen in .vscode/settings.json versioniert werden, statt sich auf individuelle, lokale Konfiguration jedes Entwicklers zu verlassen, sonst formatiert jeder Editor nach eigenen, potenziell abweichenden Regeln.
Format-on-Save reduziert die Zahl der Verstöße, die überhaupt erst den Pre-Commit-Hook erreichen, drastisch, weil der Code bereits beim Speichern korrekt formatiert wird. Wer PSR-12 durchsetzen auf allen drei Ebenen, Editor, Pre-Commit-Hook und CI, konsequent umsetzt, erlebt in der Praxis kaum noch einen tatsächlichen CI-Fehlschlag wegen Formatierung, weil die früheren Stufen die meisten Fälle bereits vorher abfangen.
7. Rollout auf Legacy-Code ohne Big-Bang-Reformat
Ein bestehendes, mehrere Jahre gewachsenes Projekt direkt mit einem einzigen riesigen Reformat-Commit auf PSR-12 zu bringen, klingt verlockend, erzeugt aber einen Diff mit tausenden geänderten Zeilen, der git blame für praktisch die gesamte Historie unbrauchbar macht und Merge-Konflikte in jedem parallel laufenden Feature-Branch provoziert. Eine schrittweise Migration ist in fast allen Fällen die bessere Strategie.
Git bietet dafür seit Version 2.23 eine gezielte Lösung: .git-blame-ignore-revs listet Commit-Hashes auf, die git blame standardmäßig überspringt, konfiguriert über git config blame.ignoreRevsFile .git-blame-ignore-revs. Ein einmaliger, vollständiger Reformat-Commit landet in dieser Datei, git blame zeigt danach für jede betroffene Zeile weiterhin den ursprünglichen, fachlichen Autor an, statt pauschal den Reformat-Commit.
# One-time full reformat, then register the commit to be ignored by blame
vendor/bin/php-cs-fixer fix
git add -A
git commit -m "style: apply PSR-12 formatting across the codebase"
# Add the resulting commit hash to .git-blame-ignore-revs
echo "$(git rev-parse HEAD) # PSR-12 mass reformat" >> .git-blame-ignore-revs
git add .git-blame-ignore-revs
git commit -m "chore: ignore PSR-12 reformat commit in git blame"
# Each contributor configures git locally (or via .gitconfig checked into
# the repo root and referenced via includeIf)
git config blame.ignoreRevsFile .git-blame-ignore-revs
Alternativ, für Projekte, bei denen selbst ein einmaliger Reformat-Commit zu riskant erscheint, etwa wegen laufender Release-Branches, bietet sich ein Baseline-Ansatz analog zu PHPStan an: Ein initialer phpcs-Lauf über die gesamte Codebasis wird als Referenzzustand gespeichert, die CI-Prüfung schlägt danach nur fehl, wenn neue oder geänderte Dateien neue Verstöße einführen, unverändert bleibender Legacy-Code wird vorerst toleriert und schrittweise bei ohnehin anstehenden Änderungen mitkorrigiert.
In der Praxis bewährt sich häufig eine Kombination: Neue Module und aktiv weiterentwickelte Verzeichnisse werden sofort vollständig auf PSR-12 durchsetzen, klar abgegrenzte, kaum noch angefasste Legacy-Bereiche bleiben über exclude-pattern zunächst ausgenommen und werden bei Gelegenheit, etwa im Rahmen eines ohnehin geplanten Refactorings, nachgezogen.
8. Was danach im Review noch übrig bleibt
Nach vollständiger Automatisierung verschwinden Kommentare zu Einrückung, Klammernposition und Leerzeilen komplett aus dem Review, das gewonnene Zeitbudget verschiebt sich vollständig auf fachliche Fragen: Ist die gewählte Architektur sinnvoll, sind Edge Cases abgedeckt, ist der Name einer Methode selbsterklärend. Diese Verschiebung ist der eigentliche Wert der Automatisierung, nicht die Formatierung selbst, sondern die dadurch frei werdende Aufmerksamkeit für Dinge, die kein Tool automatisch prüfen kann.
Ein sekundärer, oft unterschätzter Effekt: Neue Teammitglieder müssen den unternehmensinternen Style nicht mehr aus Beispielcode oder mündlicher Überlieferung ableiten, die Konfigurationsdatei selbst ist die verbindliche, ausführbare Dokumentation des Styles. Das reduziert Einarbeitungszeit und verhindert Situationen, in denen unterschiedliche Teammitglieder unterschiedliche, informelle Vorstellungen vom "richtigen" Stil vertreten.
9. PHP_CodeSniffer vs. PHP-CS-Fixer im Vergleich
Beide Tools ergänzen sich in der Praxis eher, als dass eines das andere vollständig ersetzt. Die folgende Übersicht zeigt die wichtigsten Unterschiede für die Entscheidung, welches Tool in welcher Rolle zum Einsatz kommt.
| Dimension | PHP_CodeSniffer | PHP-CS-Fixer |
|---|---|---|
| Primäres Konzept | Erkennung und Meldung von Verstößen | Automatisches Umschreiben von Code |
| Auto-Fix-Abdeckung | Teilweise, über separates phpcbf | Umfangreich, Kernfunktion |
| Regel-Katalog | Sehr breit, auch Architektur-Sniffs | Primär reine Formatierung |
| Konfigurationsformat | XML-Ruleset | PHP-Datei mit Config-Objekt |
| CI-Freundlichkeit | Checkstyle-Report für Diff-Kommentare | --dry-run --diff für nicht-blockierende Prüfung |
Die produktivste Kombination für die meisten Teams: PHP-CS-Fixer für automatisches Formatieren im Editor und im Pre-Commit-Hook, PHP_CodeSniffer für die vollständige Regelprüfung in CI, inklusive Sniffs, die über reine Formatierung hinausgehen, etwa Nutzung veralteter Funktionen oder fehlende Sichtbarkeitsmodifikatoren.
10. Zusammenfassung
PSR-12 durchsetzen gelingt nicht durch mehr Disziplin oder ausführlichere Style-Guides, sondern durch Werkzeuge, die den Zustand objektiv prüfen und wo möglich automatisch korrigieren. PHP_CodeSniffer mit einem phpcs.xml-Ruleset deckt die Erkennung ab, PHP-CS-Fixer mit .php-cs-fixer.php übernimmt das automatische Umschreiben des Großteils der Verstöße. Pre-Commit-Hooks fangen Probleme lokal ab, bevor sie überhaupt committet werden, die CI-Pipeline bleibt die letzte, nicht umgehbare Instanz.
Editor-Integration mit Format-on-Save reduziert die Zahl der überhaupt entstehenden Verstöße auf ein Minimum, ein Rollout über .git-blame-ignore-revs oder eine Baseline-Strategie ermöglicht die Migration bestehender, gewachsener Codebasen ohne einen riskanten Big-Bang-Commit. Der eigentliche Gewinn zeigt sich im Review: Kommentare zu Formatierung verschwinden vollständig, die gewonnene Aufmerksamkeit fließt in fachliche Fragen, die kein Tool automatisch beantworten kann.
PSR-12 im Team durchsetzen - Das Wichtigste auf einen Blick
Erkennung und Fix
PHP_CodeSniffer mit phpcs.xml für Erkennung, PHP-CS-Fixer mit .php-cs-fixer.php für automatisches Umschreiben.
Drei Verteidigungslinien
Editor (Format-on-Save), Pre-Commit-Hook (nur gestagte Dateien), CI-Pipeline (harte, nicht umgehbare Prüfung).
Legacy-Rollout
Einmaliger Reformat-Commit plus .git-blame-ignore-revs, oder Baseline-Ansatz für risikoscheue Projekte.
Gewonnene Zeit
Review-Kommentare zu Formatierung entfallen vollständig, Aufmerksamkeit verschiebt sich auf Architektur und Logik.
11. FAQ: PSR-12 im Team durchsetzen
1Wie kann ich PSR-12 im Team durchsetzen?
2Was unterscheidet PHP_CodeSniffer und PHP-CS-Fixer?
3Wie richte ich einen Pre-Commit-Hook ein?
4Kann ein Pre-Commit-Hook umgangen werden?
5Wie führe ich PSR-12 in Legacy-Code ein?
6Was macht .git-blame-ignore-revs?
7Wie richte ich Format-on-Save in PhpStorm ein?
8Sollte der Style-Check in CI blockierend sein?
9Ersetzt das den Code-Review komplett?
10Brauche ich beide Tools gleichzeitig?
Mironsoft
Code-Qualität, Tooling und CI-Pipelines für PHP-Teams
Noch immer Formatierungsdiskussionen im Code-Review?
Wir richten PHP_CodeSniffer und PHP-CS-Fixer sauber ein, integrieren Pre-Commit-Hooks und CI-Checks und begleiten den Rollout auf eurer bestehenden Codebasis, ohne einen riskanten Big-Bang-Reformat.
Tooling-Setup
phpcs.xml und .php-cs-fixer.php passend zu eurer bestehenden Codebasis konfiguriert
Legacy-Rollout
Reformat-Strategie mit git-blame-ignore-revs oder Baseline-Ansatz, risikoarm umgesetzt
CI-Integration
Pre-Commit-Hooks und Required Checks, die Style-Verstöße dauerhaft aus dem Review entfernen