DEV Community

Benjamin Delespierre
Benjamin Delespierre

Posted on

How to setup Git commit hooks for PHP

This very simple script will run these checks each time you commit:

  • PHP Internal Linter to check your code for syntax errors
  • PHP Code Sniffer to check for coding standard (PSR-2)
  • PHPStan to analyse the code and find bugs

Overall it takes a few seconds and it analyses only the staged files - those in green when you do a git status.

If a single check fails the error message is displayed and the commit is aborted.


[1/3] php lint            OK!
[2/3] code sniffer        OK!
[3/3] phpstan             NOK!

 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ --------------------------------------------------------------------- 
  Line   CanSendInvitation.php                                                
 ------ --------------------------------------------------------------------- 
  14     Parameter $lol of method App\Rules\CanSendInvitation::__construct()  
         has invalid typehint type App\Rules\Noob.                            
 ------ --------------------------------------------------------------------- 

 [ERROR] Found 1 error                                                          
Enter fullscreen mode Exit fullscreen mode

Step 1: requirements

You're going to need some packages to validate your code so go ahead and install:

composer install --dev phpstan/phpstan
composer install --dev squizlabs/php_codesniffer
Enter fullscreen mode Exit fullscreen mode

Step 2: create the hook

In .git/hooks/pre-commit write:

#!/usr/bin/env bash

# get bash colors and styles here: 

function __run() #(step, name, cmd)
    local color output exitcode

    printf "${C_YELLOW}[%s]${C_RESET} %-20s" "$1" "$2"
    output=$(eval "$3" 2>&1)

    if [[ 0 == $exitcode || 130 == $exitcode ]]; then
        echo -e "${C_GREEN}OK!${C_RESET}"
        echo -e "${C_RED}NOK!${C_RESET}\n\n$output"
        exit 1

modified="git diff --diff-filter=M --name-only --cached  | grep \".php$\""
phpcs="vendor/bin/phpcs --report=code --colors --report-width=80 --standard=PSR2 --ignore=${ignore}"

__run "1/3" "php lint" "${modified} | xargs -r php -l"
__run "2/3" "code sniffer" "${modified} | xargs -r ${phpcs}"
__run "3/3" "phpstan" "${modified} | xargs -r vendor/bin/phpstan analyse"
Enter fullscreen mode Exit fullscreen mode

The line

Enter fullscreen mode Exit fullscreen mode

is project specific. It's the comma separated list of paths to ignore in the PHP Code Sniffer analysis. Typically, you add the paths for which the PSR-2 makes no sense, like configuration or translation files.

You may change it to whatever you want.

My team has been using and refining this script for years and it has saved us countless hours in refactoring. The sooner you detect errors the better.

Our real script includes more tests that are specific to our app like unit & behavioral test to ensure you broke nothing. I'm sure with a little imagination you are capable to adapt the script to your project.

I suggest you play around with it and share what you find out in the comments.

A usual don't forget to like and comment, that keeps me motivated to write more content for you!

Happy coding!

Top comments (5)

mmayboy_ profile image
O ji nwayo e je

1) What's the difference between code sniffer and editor config? Is code sniffer a more language-specific form of formatting i.e. Looks into language specific things like naming convention?

2) Why do we need to run php lint first? Isn't that what the first level of php-stan/psalm does?

elcotu profile image
Daniel Coturel

Good post!

hailong profile image

It's a great sample to start with.

creativedevs profile image

I have tried this but getting below error

xargs: ./vendor/bin/phpcs: No such file or directory

it is working directly but not from pre-commit

bdelespierre profile image
Benjamin Delespierre

Add PHPCS as a dev dependency with composer require --dev squizlabs/php_codesniffer.