Enforcing a PHP coding standard using PHP_CodeSniffer – Part 1

This is the first part of a rather exciting trilogy (a thriller if you may) on how to use the PHP_CodeSniffer component from the PEAR repository to force developers to follow a set of coding standards before allowing them to commit code to a Subversion repository. This can also be accomplished using other Version Control Systems (VCS’s) but I will focus on Subversion. In this part of the trilogy I will explain the importance of having a coding standard, and I will give you a short introduction on how the PHP_CodeSniffer component works and some basic usage of it.

Let’s get on with the show!

When working together with other developers it’s important to agree on some sort of coding standard (not only in PHP). Why is this important you say? You don’t always get to play with stuff you have written yourself. Whenever you are to debug code that someone else wrote, wouldn’t it be great to know that he/she writes code that at least looks like your own?

Using a coding standard doesn’t force developers to solve problems the exact same way, but it might make the solution easier to read for other developers. It might also help developers from doing some pretty nasty errors. Let’s say you have the following piece of code:

if (isset($_COOKIE['userId']) && isset($_COOKIE['userSecret'])) {
    $actualUserSecret = fetchUserSecretCode($_COOKIE['userId']);

    if ($actualUserSecret = $_COOKIE['userSecret']) {
        // ok ... the secret code in the cookie is the same as the one
        // returned from the fetchUserSecretCode function. Let's login
        // the user

        // ...

Now … the code above is all valid, but it contains a pretty nasty bug. The line that says:

if ($actualUserSecret = $_COOKIE['userSecret']) {

is missing an equal sign, so it will always evaluate to true, which in this case will log in the user with the user id from the cookie. If you want to log in as other users you could just change the user id in the cookie, and make a new request, and voila!

This bug is pretty hard to identify since the developer who wrote it probably just didn’t press the equal sign hard enough the second time and the code produced it still valid. The developer “knows” that the error has to be somewhere else and doesn’t even look in the right place. Some of you reading this is probably saying something like: “Come on, nobody will ever manage to do something that stupid!?”. The reason I chose this as an example is because yours truly exploited this “bug” in someone’s code once, so yeah … it’s possible to write something like the example above.

Another issue is when users start to mix up tabs and spaces for indentation and have different editor settings. If you open up a script that has both tabs and spaces used for indentation and your editor has a different tab width than the one who used spaces as indentation all hell breaks loose. Suddenly you won’t even get to order breakfast from your favorite Whammy Burger because you had to spend five extra minutes removing someone’s tabs and, well, we all know what happens next…

What has this got to do with a coding standard? What if the coding standard you were said to follow specified that doing assignments inside an if-test was illegal or that the use of tabs as indentation (or tabs in your code at all) were strictly forbidden? Wouldn’t that fix this issue? That depends on how you deploy the code you are writing. To be able to enforce a standard you need to attach a check that finds out if the code you are sharing with other developers adheres to some standard. Subversion (and other VCS’s) will let you add “hooks” to different events that occur. The event we would like to attach the coding standard check to is the one that occurs before the changes in code actually gets pushed to the repository: the pre-commit event. This event, as it’s name clearly specifies, happens before the commit is done, and it has the power to stop the commit from happening.

The next question is how do we actually check the code against a standard? This is where the PHP_CodeSniffer component comes into play. As its name implies, it will actually sniff PHP code. You might have heard Ramones singing something about wanting to sniff some glue… I’ll let you in on a little secret: The original lyrics actually went a little something like this:

Now I wanna sniff some code
Now I wanna have strings to explode
All the kids wanna sniff some code
All the kids want strings to explode

Let’s get on with the show and start sniffing that code!

PHP_CodeSniffer uses something called sniffs and tokens to pull this off. A Sniff is actually a PHP class that checks one part of the coding standard only. A coding standard in PHP_CodeSniffer is a collection of these sniff classes. One example of a sniff is: “Disallow Tab Indentation”.

A token is an internal representation of a part of userland PHP code. Most of you have probably seen a token in an error message. If you do something like:

$foo = 'foo'
$foobar = $foo . 'bar';

you will get “syntax error, unexpected T_VARIABLE in … ” because you forgot to end the first line with a semi-colon. This actually shows us that some error messages in PHP aren’t all fun and games, but that’s a different story…

The T_VARIABLE part of the error message is as some of you might have guessed a token. PHP_CodeSniffer uses the Tokenizer extension in PHP to generate tokens of the PHP scripts it gets as input and the sniff classes you implement can use these tokens to analyze the input and see if it is correct according to the coding standard.

PHP_CodeSniffer comes with some complete coding standards and enough predefined sniff classes to let you define your own standard by using only predefined sniffs. If you simply want to follow the PEAR coding standard you can use the one that comes with the component. The component also includes a couple of command line scripts (Linux and Windows) that will let you examine your code with little effort.

Since the component belongs to PEAR, the default coding standard is PEAR (no shit Sherlock!). If you also want to use the PEAR standard on your code you can issue the following command (on Linux):

php /path/to/phpcs /path/to/script.php

PHP_CodeSniffer will then analyze script.php and see if it follows the PEAR standard. If not, you will get a bunch of errors informing you on the parts of your code that is different from the standard. If you have lots of code inside a directory you can simply use the name of the directory as argument to the phpcs script instead of the single script name. If you want to use some of the other coding standards that comes with the component you can do so by adding the –standard=<standard> argument. To use the “Zend” standard on a directory of code you can do something like this:

php /path/to/phpcs –standard=Zend /path/to/your/code/

And that’s it for this part actually. In the next part, which I hope to finish sometime this weekend or early next week,  I will create a coding standard in the PHP_CodeSniffer component and show you how to use some of the many sniff classes already defined to get you up and running with a shiny “new” coding standard. I will also show you how to extend some of them to do something a little different.

In the last part I will show you how to implement new sniff classes. The last part will also contain information on how to become the least popular guy on the development team: Add a check to the pre-commit hook so no one can commit code unless it follows the standard 100%!

This entry was posted in PHP, Technology and tagged , , , , , , . Bookmark the permalink.

2 Responses to Enforcing a PHP coding standard using PHP_CodeSniffer – Part 1

  1. Pingback: PHP Coding School » Blog Archive » php code [2008-06-25 23:58:00]

  2. Pingback: Enforcing a PHP coding standard using PHP_CodeSniffer - Part 2 « Christer’s blog o’ fun

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s