Zum Inhalt springen

Git Hooks und Husky: Codequalität automatisch sicherstellen

Veröffentlicht am 28. Okt. 2025 | ca. 2 Min. Lesezeit |

Das Ziel ist klar: Kein schlecht formatierter Code, keine kaputten Tests und keine vergessenen var_dump()-Aufrufe sollen im Repository landen. Git Hooks sind der Mechanismus, um das durchzusetzen — automatisch, ohne dass jeder Entwickler daran denken muss.

Was sind Git Hooks?

Git Hooks sind ausführbare Skripte im .git/hooks/-Verzeichnis eines Repositories. Git führt sie automatisch bei bestimmten Ereignissen aus:

Hook Zeitpunkt Typischer Einsatz
pre-commit Vor dem Commit Linting, Tests, Code-Formatierung
commit-msg Nach Commit-Message-Eingabe Conventional Commits prüfen
pre-push Vor dem Push Test-Suite ausführen
post-merge Nach einem Merge composer install, npm install
pre-rebase Vor einem Rebase Warnung bei ungesicherten Änderungen

Manueller Pre-Commit Hook

#!/bin/bash
# .git/hooks/pre-commit

# PHP CS Fixer auf staged PHP-Dateien anwenden
STAGED_PHP=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.php$')

if [ -n "$STAGED_PHP" ]; then
    ./vendor/bin/php-cs-fixer fix $STAGED_PHP --no-interaction
    git add $STAGED_PHP
fi

Das Problem: .git/hooks/ wird nicht in das Repository eingecheckt, also müssen alle Entwickler den Hook manuell installieren.

Husky: Hooks versionieren

Husky löst dieses Problem: Git Hooks werden im Repository gespeichert und automatisch für alle Entwickler aktiviert.

Installation

# Husky installieren
npm install --save-dev husky

# Husky initialisieren
npx husky init

Das erstellt .husky/pre-commit und fügt "prepare": "husky" zu package.json hinzu. Jetzt wird Husky automatisch beim npm install eingerichtet.

Pre-Commit Hook mit Husky

# .husky/pre-commit
#!/bin/sh

npx lint-staged

lint-staged: Nur geänderte Dateien prüfen

lint-staged führt Linter und Formatter nur auf den für den Commit gestagten Dateien aus — nicht auf dem gesamten Projekt. Das macht Hooks deutlich schneller.

package.json Konfiguration

{
    "scripts": {
        "prepare": "husky"
    },
    "lint-staged": {
        "*.ts": [
            "eslint --fix",
            "prettier --write"
        ],
        "*.scss": [
            "stylelint --fix"
        ],
        "*.php": [
            "./vendor/bin/php-cs-fixer fix --no-interaction"
        ]
    },
    "devDependencies": {
        "husky": "^9.0.0",
        "lint-staged": "^15.0.0",
        "eslint": "^9.0.0",
        "prettier": "^3.0.0"
    }
}

PHP CS Fixer konfigurieren

PHP CS Fixer ist das Standard-Tool für PSR-12-konforme PHP-Formatierung in Symfony-Projekten.

.php-cs-fixer.dist.php

<?php

declare(strict_types=1);

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__ . '/src')
    ->in(__DIR__ . '/tests')
    ->name('*.php')
    ->exclude('var')
    ->exclude('vendor');

return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        '@Symfony' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'no_unused_imports' => true,
        'trailing_comma_in_multiline' => true,
        'phpdoc_align' => ['align' => 'vertical'],
        'declare_strict_types' => true,
        'single_quote' => true,
    ])
    ->setFinder($finder)
    ->setCacheFile(__DIR__ . '/.php-cs-fixer.cache');

.php-cs-fixer.cache in .gitignore

.php-cs-fixer.cache

Commit-Message-Validierung

Conventional Commits ist ein Standard für strukturierte Commit-Messages:

type(scope): description

feat(auth): add OAuth2 login support
fix(cart): prevent negative quantities
docs(api): update endpoint documentation

commit-msg Hook

# .husky/commit-msg
#!/bin/sh

npx --no -- commitlint --edit "$1"

commitlint.config.js

export default {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'type-enum': [2, 'always', [
            'feat',
            'fix',
            'docs',
            'style',
            'refactor',
            'test',
            'chore',
            'revert',
        ]],
        'subject-max-length': [2, 'always', 100],
        'subject-case': [0], // Deutsch/Englisch gemischt erlauben
    },
};
npm install --save-dev @commitlint/cli @commitlint/config-conventional

Pre-Push Hook mit PHPUnit

Vor einem Push können Tests ausgeführt werden:

# .husky/pre-push
#!/bin/sh

echo "Running PHPUnit tests..."
vendor/bin/phpunit --testdox --no-coverage

if [ $? -ne 0 ]; then
    echo "Tests failed! Push aborted."
    exit 1
fi

Für DDEV-Projekte:

# .husky/pre-push
#!/bin/sh

echo "Running PHPUnit tests in DDEV..."
ddev exec vendor/bin/phpunit --no-coverage

if [ $? -ne 0 ]; then
    echo "Tests failed! Push aborted."
    exit 1
fi

Post-Merge Hook: Dependencies automatisch aktualisieren

Nach einem Merge oder Checkout können Composer- und npm-Pakete automatisch installiert werden:

# .husky/post-merge
#!/bin/sh

# Prüfen ob composer.lock sich geändert hat
CHANGED_FILES=$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)

if echo "$CHANGED_FILES" | grep -q "composer.lock"; then
    echo "composer.lock changed, running composer install..."
    ddev exec composer install
fi

if echo "$CHANGED_FILES" | grep -q "package-lock.json"; then
    echo "package-lock.json changed, running npm install..."
    ddev exec npm install
fi

Vollständige Konfiguration für ein Symfony-Projekt

{
    "scripts": {
        "prepare": "husky"
    },
    "lint-staged": {
        "*.php": [
            "./vendor/bin/php-cs-fixer fix --no-interaction"
        ],
        "*.{ts,js}": [
            "eslint --fix"
        ],
        "*.scss": [
            "stylelint --fix"
        ],
        "*.{json,md,yaml,yml}": [
            "prettier --write"
        ]
    }
}

.husky/pre-commit:

#!/bin/sh
npx lint-staged

.husky/commit-msg:

#!/bin/sh
npx --no -- commitlint --edit "$1"

Hooks überspringen (nur in Ausnahmefällen!)

# Einzelnen Hook überspringen
git commit --no-verify -m "WIP: work in progress"

# Nur für Notfälle — nie in normalen Workflows!
HUSKY=0 git push

Hinweis: --no-verify sollte im Team-Setup eigentlich nicht möglich sein. CI/CD-Pipelines dienen als letzte Verteidigungslinie, falls jemand Hooks umgeht.

Fazit

Git Hooks mit Husky und lint-staged sind eine der effektivsten Maßnahmen für konsistente Codequalität im Team. Einmal eingerichtet, läuft die Code-Formatierung automatisch — Entwickler müssen nicht mehr an PHP CS Fixer denken. Conventional Commits helfen bei automatischen Changelogs und aussagekräftiger Git-History. Für Symfony-Projekte empfehle ich diese Kombination:

  1. Husky für Hook-Management
  2. lint-staged für selektives Linting
  3. PHP CS Fixer mit PSR-12 + Symfony-Ruleset
  4. commitlint für Conventional Commits
  5. Pre-Push PHPUnit für Test-Sicherheit
Thomas Wunner

Thomas Wunner

Fachinformatiker für Anwendungsentwicklung mit Ausbildereignungsprüfung und über 14 Jahre Erfahrung im Aufbau skalierbarer Webanwendungen mit Symfony und Shopware. Abseits der Tastatur ist Thomas als Rettungsschwimmer in der Wasserwacht aktiv, legt als DJ auf und erkundet die Umgebung auf dem Motorrad.

Kommentare

Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.