saylornotes

The Blog of Chris Saylor

Search Results

    Enforce code standards with composer, git hooks, and phpcs

    April 14, 2014 engineering

    Maintaining code quality on projects where there are many developers contributing is a tough assignment. How many times have you tried to contribute to an open-source project only to find the maintainer rejecting your pull request on the grounds of some invisible coding standard? How many times as a maintainer of an open-source project (or internal) have you had a hard time reading code because there were careless tabs/spaces mixed, if statements with no brackets, and other such things. Luckily there are tools that can assist maintainers. In this post, I’ll be going over how to use composer, git hooks, and phpcs to enforce code quality rules.

    There are a couple of things to keep in mind. First, you want this process to be as simple as possible. Secondly, you want it to be easy to run when necessary. Finally, you want it to be universally accepted as part of your contribution procedure.

    What if I told you that it could be done without the developer even knowing it’s happening?

    Most modern PHP projects use composer as their dependency manager. Before you can make anything work, you need to run composer install. This is where the magic happens.

    First, we need a development dependency specified to install phpcs. It looks something like this:

    {
        "require-dev": [
            "squizlabs/php_codesniffer": "2.0.*@dev"
        ]
    }
    

    Composer has a handy schema entry called scripts. It supports a script hook post-install-cmd. We will use this to install a git pre-commit hook. Adding to our example above:

    {
        "require-dev": [
            "squizlabs/php_codesniffer": "2.0.*@dev"
        ],
        "scripts": {
            "post-install-cmd": [
                "bash contrib/setup.sh"
            ]
        }
    }
    

    This will run a bash script called setup.sh when the command composer install is run.

    In our setup.sh, we will need to copy a pre-commit script into the .git/hooks directory:

    #!/bin/sh
    
    cp contrib/pre-commit .git/hooks/pre-commit
    chmod +x .git/hooks/pre-commit
    

    This will copy our pre-commit script from the contrib directory to the hooks section of the special git directory and make it executable.

    Whenever a contributing developer attempts to commit their code, it will run our pre-commit script. Now all we need to do is run the code sniffer rules on relavent files specific to this commit:

    #!/bin/sh
    
    PROJECT=`php -r "echo dirname(dirname(dirname(realpath('$0'))));"`
    STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\\\.php`
    
    # Determine if a file list is passed
    if [ "$#" -eq 1 ]
    then
    	oIFS=$IFS
    	IFS='
    	'
    	SFILES="$1"
    	IFS=$oIFS
    fi
    SFILES=${SFILES:-$STAGED_FILES_CMD}
    
    echo "Checking PHP Lint..."
    for FILE in $SFILES
    do
    	php -l -d display_errors=0 $PROJECT/$FILE
    	if [ $? != 0 ]
    	then
    		echo "Fix the error before commit."
    		exit 1
    	fi
    	FILES="$FILES $PROJECT/$FILE"
    done
    
    if [ "$FILES" != "" ]
    then
    	echo "Running Code Sniffer..."
    	./vendor/bin/phpcs --standard=PSR1 --encoding=utf-8 -n -p $FILES
    	if [ $? != 0 ]
    	then
    		echo "Fix the error before commit."
    		exit 1
    	fi
    fi
    
    exit $?
    

    This script will get the staged files of the commit, run a php lint check (always nice), and apply the code sniffer rules to the staged files.

    If there is a code standards violation, the phpcs process will return a non-zero exit status which will tell git to abort the commit.

    With all of these things in place, the workflow is as follows:

    • Developer runs composer install.
    • PHP Code Sniffer is installed via a dev dependency.
    • The post-install command automatically copies the pre-commit hook into the developer’s local git hooks.
    • When the developer commits code, the pre-commit hook fires and checks the staged files for coding standards violations and lint checks.

    This is a relatively simple setup that can save pull request code reviews a significant amount of time preventing back-and-forth on simple things such as mixed tabs/spaces, bracket placement, etc.

    Related Posts

    Meta: How this blog is built and deployed April 11, 2019

    It is an unspoken rule that if you utilize something other than Wordpress for a blog that you must include an article on how it is built. This is that …

    Building a Chess bot for Slack August 23, 2018

    With Atlassian’s announcement suspending development of Stride and dropping support for Hipchat in favor of Slack, I decided that the time was right …

    Why Use Generators in PHP January 2, 2017

    I’ve heard many co-workers, friends, and colleagues at meetups acknowledge the existence of generators in PHP, but not understand why they would use …

    Elasticsearch Series: Let It Work for You May 19, 2016

    This is the second entry in a series on Elasticsearch and how we use it in our applications. See the previous entry on Rebuilding Indices with No …

    comments powered by Disqus