PHP Codesniffer - Using Custom Standards on a Per Project Basis
By: Roger Creasy
What is PHP Codesniffer?
PHP Codesniffer is a development tool that inspects your code, checks for standards violations against a coding standard, and reports those violations to you. This post is not about installing and configuring Codesniffer. I make the assumption that you have installed it and are basically familiar with how it works. In this post we will take a look at how to customize the coding standard against which Codesniffer checks.
Reasons for Using a Custom Standard
Why use a custom standard? Why not simply use PSR2? In my day job I work on a very large, 800K lines, code base. Parts of this code base are around 10 years old. To bring the entire application up to PSR2 standards at once would be more than our small team could accomplish. So, we start with teh PSR2 standard, but alter it to make working with our codebase easier. As we get through updates to the application, we remove alterations. Eventually we will be at PSR2.
The Ruleset
PHP Codesniffer uses an XML file named ruleset.xml to determine which rules, Codesniffer calls them "sniffs", to use. You can place this file anywhere as long as you tell Codesniffer where to find it, if you put it outside of the default location. This means that a team can have a ruleset for each project, and include that ruleset in version control. That way, onboarding new devs to your standards is as easy as setting up Codesniffer to find the ruleset.
To get an idea of what a ruleset file looks like, take a look at the PSR2 ruleset.xml; it is included by default in the installation of Codesniffer. The included default standards ruleset files are in the src/Standards
directory. I installed Codesniffer using Composer. So, for me the PSR2 ruleset is at ~/.config/composer/vendor/squizlabs/php_codesniffer/src/Standards/PSR2/ruleset.xml (This is on a Fedora box. Debian based systems, like Ubuntu, place the global composer vendor directory at ~/.composer/vendor/
. To find your composer global vendor directory, run composer config --list --global
). Or, view the file in your browser from the Github repo Codesniffer PSR2 standards ruleset
Note the line <rule ref="PSR1"/>
. As the code comment says, this line imports the entire PSR1 standards set. When we get to developing our ruleset, we will use this import feature.
Next, take a look at these lines:
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="120"/>
<property name="absoluteLineLimit" value="0"/>
</properties>
</rule>
This section of XML sets up the rule Generic.Files.Linelength
, which is the rule that limits the length of each line to 120 characters. Browse through the other lines in the ruleset. The PSR2 ruleset is very well commented. I am sure most of the file will make sense.
Creating a Custom Ruleset
You could create an entire ruleset from scratch. I know there are likely times when that is a good idea. But, most of you are probably like me and you basically use PSR2, and only need to change a few rules. A quick overview of how to do that is:
- Set up the XML file so Codesniffer can read it
- Pull in PSR2
- Exclude rules that you don't want at all
- Customize rules that you need to change
Set up the file
Let's create our file in the root of the project we want it to affect. I put the custom standard, or ruleset, in the root of my projects. This allows for standards that are customized for each project, allows the customization to be included in version control, and lets me use PSR2 for side or new projects within my development environment.
The XML ruleset file must be set up correctly in order for Codesniffer to find and use it. The XML opening tag of course must be there. Also, there must be a ruleset tag as the primary tag. This tag defines the name of the ruleset. Fire up your favorite text editor or IDE and create file named phpcs.xml in your project's root. Enter:
<?xml version="1.0"?>
<ruleset name="Custom Standard">
You can name your ruleset anything you want.
Pull in the base ruleset
Next, you want to include the standard you are using as a base. In our case that is PSR2. add this line to your file:
<rule ref="PSR2"/>
Since the PSR2 ruleset pulls in the PSR1 ruleset, as well as others, our ruleset will include those as well. We are basically extending a ruleset that extends other rulesets. Notice that I wrote the tag as a self-closing tag. As we saw earlier, the rule tag can include other tags. So, it is not always self-closing.
Finding sniffs' names
Deciding which sniffs to exclude is our first challenge. I am assuming that PHP Codesniffer is giving you unwanted errors and warnings. Figuring out which sniff generates each warning from just the warning itself is difficult. Codesniffer gives us a command line switch that makes it easy. Yes, if you are using Codesniffer through an IDE or text editor, you will have to use the command line. But, do not fear; it is easy.
From within your project's root directory (get there by cd /path/to/project
) run phpcs path/to/file/filename.php -s
. Note that there is no opening slash in the path to the file. This command will output a list of warnings and errors. On each of these lines, inside parenthesis is the sniff that generated the error or warning. i.e. (Generic.Files.LineLength.TooLong)
, (PSR1.Classes.ClassDeclaration.MissingNamespace)
, (Squiz.Classes.ValidClassName.NotCamelCaps)
, or (PSR1.Methods.CamelCapsMethodName.NotCamelCaps)
.
Exclude unwanted rules
We'll get to the first sniff in a bit. Let's look at the other 3 first. Invalid class names, invalid method names, and no namespace errors are not errors we can correct within just one file. In fact, changing these things could impact many files. Correcting them would likely be very involved and time-consumming. We want to ignore these errors for now, and correct them later. Not the 'right' way to handle them. But, in the reality of too many projects and not enough time, it is the best solution.
To exclude these sniffs from PSR2 change the self-closing rule tag to the following:
<rule ref="PSR2">
<exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/>
<exclude name="Squiz.Classes.ValidClassName.NotCamelCaps"/>
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps"/>
</rule>
Pretty straightforward, right? After we tell Codesniffer how to find our ruleset, Codesniffer will ignore those sniffs in its checks. (I tried to choose 3 examples that are common issues with legacy code bases).
Altering existing rules
What if instead of excluding a rule, you want to alter it? Most rules have properties which can be overridden. As an example of how this works, lets go back to our list of problem sniffs. In this example we will look at (Generic.Files.LineLength.TooLong)
.
Our work codebase has many lines that are much longer than 120 characters, the default limit. Our developers have large screens, so increasing this value has no real impact. Here is the code as it is in my custom ruleset:
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="170"/>
<property name="absoluteLineLimit" value="0"/>
</properties>
</rule>
Telling Codesniffer how to find our ruleset
How Codesniffer finds your ruleset depends on how it is being called.
I write code in Vim, and I use the syntastic plugin to manage Codesniffer. This means that my Codesniffer configuration is in my .vimrc file. If you use Vim, to use your custom ruleset, do not set the standard. I had the line let g:syntastic_php_phpcs_args = "--standard=PSR2"
Delete or comment out this line.
To use a custom file in PHPStorm, got to Settings/Preferences, expand the Quality tools section, and under Coding Standards select Custom. Then browse to the location of your ruleset.
In VScode you can have a custom ruleset for each project by adding an entry to your configuration. See the code below.
{
"phpcs.standard": "./phpcs.xml"
}
This tells Codesniffer to lok for the ruleset in the root of your project.
Other editors or IDEs likely use a format similar to one of the above. Explaining all possiblilities in detail is beyond the scope of this post.
If you use Codesniffer from the command line, you can specify which ruleset to use with a switch $ phpcs --standard=/path/to/custom_ruleset.xml test.php
. One note -- if you use the -s switch explained above after changing your ruleset, Codesniffer will list the rules based on your specified ruleset.
Conclusion
PHP Codesniffer is a powerful tool that helps PHP developers write standards-compliant code. The ability to customize those standards on a per project basis makes it possible to mold the rules to fit our specific needs. Hopefully this post has given you what you need to get started in that direction. Please send me any critiques, additions, or corrections. I am happy to improve this post to make it more useful. My contact information is in the footer.