Blog / Article #75
Git Hooks : PHPStan et PHPUnit en pre-commit pour sauver ta CI (et ton karma)

star

15 Août 2026
icônes PHPStan et PHPUnit

Ta CI te fait des crises de larmes à chaque push ? Tes collègues te regardent avec ce mélange de pitié et de mépris quand ton build échoue pour la énième fois ? Respire. On a tous connu ça. Mais aujourd’hui, on va te donner les clés pour que tes commits soient aussi propres qu’un appartement après le passage de Marie Kondo – sauf que là, c’est ton code qui va étinceler.

Le secret ? Un pre-commit hook qui combine PHPStan et PHPUnit. Parce que oui, attendre que la CI te hurle dessus pour corriger tes erreurs, c’est comme attendre d’avoir une crise cardiaque pour arrêter la malbouffe. Spoiler : c’est trop tard.

Pourquoi un pre-commit hook ? Parce que la procrastination, c’est mal

Tu connais le syndrome du "je corrigerai plus tard" ? Ce petit démon qui te chuchote à l’oreille : "Allez, commit maintenant, tu fixeras les warnings de PHPStan demain." Sauf que demain, c’est aujourd’hui, et aujourd’hui, c’est la CI qui explose. Et là, tu te retrouves à jouer au jeu des 7 erreurs entre ton code et les logs de GitHub Actions.

Un pre-commit hook, c’est ton garde-fou. Il vérifie ton code avant que tu ne le pushes, comme un videur de boîte qui refuserait l’entrée aux commits bourrés d’erreurs. Et le meilleur ? Tu peux le configurer pour qu’il lance PHPStan (pour les erreurs de type) et PHPUnit (pour les tests qui plantent), histoire de couvrir les deux fronts.

Bonus : tu économises du temps de CI. Parce que lancer un build pour une erreur de typo dans un commentaire, c’est comme appeler les pompiers parce que t’as oublié d’éteindre la bougie. Overkill.

Installation : le combo gagnant PHPStan + PHPUnit

On va pas se mentir, configurer un hook Git, c’est un peu comme monter un meuble IKEA : ça a l’air simple sur le papier, mais au bout de 10 minutes, t’as trois vis en trop et un sentiment de profonde injustice. Alors voici la recette, étape par étape, sans les vis en trop.

D’abord, installe les outils si ce n’est pas déjà fait. On suppose que tu as composer sous la main :

composer require --dev phpstan/phpstan phpunit/phpunit

Ensuite, crée un fichier .git/hooks/pre-commit (ou modifie-le s’il existe déjà). Attention, ce fichier doit être exécutable :

chmod +x .git/hooks/pre-commit

Maintenant, le cœur du réacteur. Voici un exemple de script qui lance PHPStan et PHPUnit uniquement sur les fichiers modifiés (parce que lancer une analyse complète à chaque commit, c’est comme passer l’aspirateur dans toute la maison pour un grain de poussière sur la table).

#!/bin/sh

# Récupère la liste des fichiers modifiés
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(php)$')

if [ -z "$STAGED_FILES" ]; then
    echo "Aucun fichier PHP modifié, commit autorisé."
    exit 0
fi

# Lance PHPStan sur les fichiers modifiés
echo "\nRunning PHPStan..."
./vendor/bin/phpstan analyse $STAGED_FILES --level=5
PHPSTAN_EXIT_CODE=$?

if [ $PHPSTAN_EXIT_CODE -ne 0 ]; then
    echo "\nPHPStan a trouvé des erreurs. Commit bloqué."
    exit 1
fi

# Lance PHPUnit sur les tests affectés par les modifications
echo "\nRunning PHPUnit..."
./vendor/bin/phpunit
PHPUNIT_EXIT_CODE=$?

if [ $PHPUNIT_EXIT_CODE -ne 0 ]; then
    echo "\nPHPUnit a échoué. Commit bloqué."
    exit 1
fi

# Si tout est bon, on autorise le commit
echo "\n✅ PHPStan et PHPUnit sont contents. Commit autorisé."
exit 0

Quelques explications :

  • git diff --cached --name-only --diff-filter=ACM : récupère la liste des fichiers modifiés et prêts à être commités. Le --diff-filter=ACM exclut les fichiers supprimés (D) et les fichiers non suivis (U).
  • grep -E '\.(php)$' : filtre pour ne garder que les fichiers PHP. Parce que oui, analyser un .md avec PHPStan, c’est un peu comme essayer de faire cuire des pâtes dans un grille-pain.
  • --level=5 : niveau de sévérité de PHPStan. Tu peux ajuster (de 0 à 9), mais 5, c’est un bon compromis entre rigueur et réalisme. Si tu mets 9, prépare-toi à pleurer.

Les pièges à éviter : ne fais pas comme ces gens

Un pre-commit hook, c’est puissant, mais mal configuré, ça peut vite devenir ton pire ennemi. Voici les erreurs à ne pas commettre :

  • Lancer une analyse complète à chaque commit : si ton projet fait 500 fichiers, ton hook va mettre 5 minutes à s’exécuter. Spoiler : tu vas le désactiver au bout de 3 commits. Limite-toi aux fichiers modifiés, comme dans l’exemple ci-dessus.
  • Ignorer les warnings : si PHPStan te sort 50 warnings, c’est pas une raison pour les ignorer. Corrige-les, ou au moins, configure PHPStan pour qu’il les traite comme des erreurs (--error-format=raw peut aider).
  • Oublier de rendre le script exécutable : un pre-commit qui ne s’exécute pas, c’est comme un parapluie dans le désert. Ça sert à rien. Vérifie avec ls -la .git/hooks/pre-commit que le fichier a bien les droits d’exécution.
  • Ne pas tester le hook avant de le pousser : rien de pire que de pousser un hook qui bloque tous les commits. Teste-le en local d’abord, comme un vrai pro.

Et surtout, n’oublie pas : un hook, c’est pas une excuse pour écrire du code de merde. C’est juste un filet de sécurité. Si tu commites du code qui ressemble à un plat de spaghettis, même PHPStan et PHPUnit ne pourront rien pour toi.

Et si mon hook plante ? (Spoiler : c’est de ta faute)

Ton hook bloque ton commit ? Félicitations, tu viens de découvrir une erreur dans ton code. Plutôt que de râler, voici comment débugger :

  • Lis les logs : le script te dit clairement ce qui cloche. Si PHPStan te sort une erreur de type, corrige-la. Si PHPUnit échoue, regarde quel test plante.
  • Désactive temporairement le hook : si t’es vraiment pressé (et que t’assumes de faire pleurer ta CI plus tard), tu peux bypasser le hook avec git commit --no-verify. Mais attention, c’est comme sauter en parachute sans vérifier son équipement : ça peut mal finir.
  • Vérifie les chemins : si ton script ne trouve pas ./vendor/bin/phpstan, c’est peut-être parce que tu es dans le mauvais répertoire. Utilise des chemins absolus si nécessaire.

Et si vraiment tu veux faire les choses proprement, ajoute un peu de couleur à tes logs pour que ce soit plus lisible. Parce que oui, un terminal en noir et blanc, c’est triste à mourir. Voici un exemple de script amélioré :

#!/bin/sh

RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(php)$')

if [ -z "$STAGED_FILES" ]; then
    echo "${GREEN}Aucun fichier PHP modifié, commit autorisé.${NC}"
    exit 0
fi

echo "\n${GREEN}Running PHPStan...${NC}"
./vendor/bin/phpstan analyse $STAGED_FILES --level=5
PHPSTAN_EXIT_CODE=$?

if [ $PHPSTAN_EXIT_CODE -ne 0 ]; then
    echo "\n${RED}PHPStan a trouvé des erreurs. Commit bloqué.${NC}"
    exit 1
fi

echo "\n${GREEN}Running PHPUnit...${NC}"
./vendor/bin/phpunit
PHPUNIT_EXIT_CODE=$?

if [ $PHPUNIT_EXIT_CODE -ne 0 ]; then
    echo "\n${RED}PHPUnit a échoué. Commit bloqué.${NC}"
    exit 1
fi

echo "\n${GREEN}✅ PHPStan et PHPUnit sont contents. Commit autorisé.${NC}"
exit 0

Là, au moins, tu sauras tout de suite si c’est la fête ou si t’as merdé.

Conclusion : un hook, c’est comme un bon café

Un pre-commit hook bien configuré, c’est comme un bon café le matin : ça te réveille, ça te donne de l’énergie, et surtout, ça t’évite de faire des conneries avant d’être pleinement opérationnel. En combinant PHPStan et PHPUnit, tu t’assures que ton code est à la fois solide et testé avant même d’arriver en CI.

Alors oui, ça demande un peu de setup. Oui, ça peut te bloquer de temps en temps. Mais au final, c’est toujours moins douloureux que de voir ta CI exploser en production. Et puis, avoue : rien ne vaut ce petit sentiment de supériorité quand ton build passe du premier coup, alors que ton collègue râle parce que son pre-commit lui a sauvé la mise (encore).

Maintenant, à toi de jouer. Et surtout, n’oublie pas : un hook, c’est comme un préservatif. Ça peut sembler chiant sur le moment, mais tu seras bien content de l’avoir quand les choses dérapent.

Publié le 15/08/2026 · 7 min de lecture Partager X LinkedIn

← Précédent Ripgrep : grep est mort, vive rg !