Using PHP Codesniffer With Laravel
Published on by Paul Redmond
As of Laravel 9, the excellent Laravel Pint comes bundled with a new Laravel installation. I love Pint, and for most people, using it for all code-style formatting is enough. Before Laravel Pint, I preferred a combination of PHP CS Fixer and PHP Codesniffer—both are excellent in tandem and offer unique rules that can help enforce code style.
Line length style is one example of where PHP Codesniffer can complement tools like PHP CS Fixer, and we can use both to help developers stick to a consistent code style. If you're using PSR-12, the Lines section has the following information about line length:
There MUST NOT be a hard limit on line length.
The soft limit on line length MUST be 120 characters.
Lines SHOULD NOT be longer than 80 characters; lines longer than that SHOULD be split into multiple subsequent lines of no more than 80 characters each.
Our CI tools can enforce both the soft and hard limits of line length, but the way I interpret PSR-12 is that PHP Codesniffer should warn us and not return any errors for lines > 80
in length. We will cover this further on in the article.
Let's work through setting up PHP Codesniffer in a new Laravel project, and then in follow-up tutorials, we will learn how to create a custom PHP Codesniffer standard that we can share across all our projects.
Along the way, I'll show you some configuration settings I like to set to get the most out of PHP Codesniffer during development.
Setup
The first thing we'll do is created an example project so you can see how to incorporate PHP Codesniffer with Laravel from scratch:
laravel new phpcs-part-1 --gitcd phpcs-part-1
The git flag will install a new Laravel application, initialize git, and commit everything to version control. This leaves us a clean slate to see the changes we make during this tutorial.
Next, let's install PHP Codesniffer as a development dependency:
composer require --dev squizlabs/php_codesniffer
Note: At the time of writing, the required command installs version ^3.7
, but your version might differ slightly, which is not a concern.
If you run phpcs
from the command line, you'll see that it wants you to specify a path:
$ vendor/bin/phpcsERROR: You must supply at least one file or directory to process. Run "phpcs --help" for usage information
Don't worry, we are about to add a config file to specify which paths PHPCS should include (and exclude), but if you run it with the default ruleset (Pear) you'll get a bunch of errors with a default Laravel install:
$ vendor/bin/phpcs -v appRegistering sniffs in the PEAR standard... DONE (28 sniffs registered).... FILE: /Users/paul/code/sandbox/phpcs-part-1/app/Providers/AppServiceProvider.php--------------------------------------------------------------------------------FOUND 2 ERRORS AFFECTING 2 LINES-------------------------------------------------------------------------------- 2 | ERROR | Missing file doc comment 7 | ERROR | Missing doc comment for class AppServiceProvider--------------------------------------------------------------------------------
If you inspect the exit code, you'll see the errors cause PHPCS to exit with a non-zero code:
$ echo $?2
I want to be clear: these errors are not because our code has bad formatting (quite the opposite) but because we are using the default PEAR standard, which Laravel doesn't follow out-of-the-box.
Let's look at using a different standard and creating a configuration file.
Exploring the Command Line Tools
Before we add a configuration file to our project, let's see how the same configuration would look using the command line. We know that we don't want to use the PEAR standard, so let's first run our PHPCS command with the built-in PSR-12 standard:
vendor/bin/phpcs --standard=PSR12 app
If you look carefully at the output, you'll notice that the PSR12 standard for PHP Codesniffer wants to fix some spacing around concatenated strings:
FILE: /Users/paul/code/sandbox/phpcs-part-1/app/Console/Kernel.php----------------------------------------------------------------------FOUND 2 ERRORS AFFECTING 1 LINE---------------------------------------------------------------------- 28 | ERROR | [x] Expected at least 1 space before "."; 0 found 28 | ERROR | [x] Expected at least 1 space after "."; 0 found----------------------------------------------------------------------PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY----------------------------------------------------------------------
The x
means we can fix these sniffs automatically (not all PHP Codsniffer sniffs are automatically fixable) with the phpcbf
bin that ships with Codesniffer. We will get to that in a second.
First, let's see about a few other command flags that I prefer to use. Once you start using PHP Codesniffer in development and CI, I notice that the full name of the sniff is not apparent. You might want to tweak that sniff, ignore it on a specific line, etc.
To always see the full sniff in the command line, you can use the `-s' flag:
vendor/bin/phpcs -s --standard=PSR12 app
Here's an example of what the output looks like with the full sniff:
FILE: /Users/paul/code/sandbox/phpcs-part-1/app/Console/Kernel.php----------------------------------------------------------------------------------------------------------------FOUND 2 ERRORS AFFECTING 1 LINE---------------------------------------------------------------------------------------------------------------- 28 | ERROR | [x] Expected at least 1 space before "."; 0 found | | (PSR12.Operators.OperatorSpacing.NoSpaceBefore) 28 | ERROR | [x] Expected at least 1 space after "."; 0 found | | (PSR12.Operators.OperatorSpacing.NoSpaceAfter)----------------------------------------------------------------------------------------------------------------PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY----------------------------------------------------------------------------------------------------------------
You can see the PSR12.Operators.OperatorSpacing.NoSpaceBefore
sniff found in the above example. If we want to ignore this sniff in the Kernel.php
file, we can add this to the app/Console/Kernel.php
file around line 28:
// phpcs:disable PSR12.Operators.OperatorSpacing.NoSpaceAfter$this->load(__DIR__.'/Commands');// phpcs:enable
If we rerun the command—this time for the Kernel.php
file—we can see that the NoSpaceAfter
rule is disabled for that line, but we still get the NoSpaceBefore
error:
vendor/bin/phpcs -s --standard=PSR12 app/Console/Kernel.php FILE: /Users/paul/code/sandbox/phpcs-part-1/app/Console/Kernel.php----------------------------------------------------------------------------------------------------------------FOUND 1 ERROR AFFECTING 1 LINE---------------------------------------------------------------------------------------------------------------- 29 | ERROR | [x] Expected at least 1 space before "."; 0 found | | (PSR12.Operators.OperatorSpacing.NoSpaceBefore)----------------------------------------------------------------------------------------------------------------PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY----------------------------------------------------------------------------------------------------------------
We can disable multiple rules for a given set of lines by comma-separating the sniffs like so:
// phpcs:disable PSR12.Operators.OperatorSpacing.NoSpaceAfter, PSR12.Operators.OperatorSpacing.NoSpaceBefore$this->load(__DIR__.'/Commands');// phpcs:enable
Finally, let's revert all the changes made to the app/Console/Kernel.php
file. You'll discover that these two rules we have been using as an example directly conflict with Laravel Pint, which expects spaces before and after.
First, let's automatically fix the above sniffs using phpcbf
:
vendor/bin/phpcbf --standard=PSR12 app PHPCBF RESULT SUMMARY----------------------------------------------------------------------------------------------FILE FIXED REMAINING----------------------------------------------------------------------------------------------/Users/paul/code/sandbox/phpcs-part-1/app/Models/User.php 1 0/Users/paul/code/sandbox/phpcs-part-1/app/Http/Controllers/Controller.php 1 0/Users/paul/code/sandbox/phpcs-part-1/app/Console/Kernel.php 2 0----------------------------------------------------------------------------------------------A TOTAL OF 4 ERRORS WERE FIXED IN 3 FILES----------------------------------------------------------------------------------------------
After running that, let's run Laravel Pint to see it do the reverse of what PHP Codesniffer fixed automatically:
$ vendor/bin/pint FIXED 54 files, 1 style issue fixed ✓ app/Console/Kernel.php concat_space
For us to be able to use both tools during development and in CI environments, we need to pick a style and enforce it with one tool and disable it from the other. I prefer automatic fixes from Pint, so I disable the competing Sniff in PHP Codesniffer.
Configuration File
Let's resolve the conflict between Pint and Codesniffer by creating a configuration file. In an application, I would typically create a phpcs.xml
file, but if you plan on letting others use your application as a starting point, you could consider using phpcs.xml.dist
. Let's create it as phpcs.xml
:
<?xml version="1.0"?><!-- @see https://pear.php.net/manual/en/package.php.php-codesniffer.annotated-ruleset.php --><ruleset name= "Laravel PHPCS Rules"> <description>PHPCS ruleset for Example app.</description> <file>tests</file> <file>app</file> <!-- Show progress of the run --> <arg value= "p"/> <!-- Show sniff codes in all reports --> <arg value= "s"/> <!-- Our base rule: set to PSR12 --> <rule ref="PSR12"> <exclude name="PSR12.Operators.OperatorSpacing.NoSpaceBefore"/> <exclude name="PSR12.Operators.OperatorSpacing.NoSpaceAfter"/> </rule> <rule ref= "Generic.Files.LineLength"> <properties> <property name="lineLimit" value="80"/> <property name="absoluteLineLimit" value="120"/> </properties> </rule> <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps"> <exclude-pattern>tests/</exclude-pattern> </rule> </ruleset>
You can see that I've added our -s
argument to the config, so all reports show sniff codes. We also target the app
and test
directories but feel free to configure other directories in your project that you want Codesniffer to check.
We've disabled the operator spacing sniffs, so Pint should be happy, and PHP Codesniffer will ignore these rules now.
We also customized the line length rule, using the language in PSR-12 as our guide for the line limit and maximum value. These sniffs report as warnings, so when you run this tool in CI you'll want to make sure that either warnings are suppressed or that CI still passes with warnings. The warnings are intended to help you, the developer, fix line lengths in development.
Finally, the NotCamelCaps
sniff is excluded in the tests
folder because I like writing PHPUnit tests with snake case:
/* @test */public function it_does_something_awesome()
Now we can run phpcs
without any arguments, and it will pick up our configuration file. You should only see line length warnings about line length and multiple imports.
vendor/bin/phpcs
Another violation you'll notice is the MultipleImport
sniff, which is due to this line, for example:
class User extends Authenticatable{ use HasApiTokens, HasFactory, Notifiable; // ...}
Feel free to disable this sniff if you want, or you can automatically fix it (using our configuration file) using the phpcbf
command-line bin:
vendor/bin/phpcbf.........F..........F.. 23 / 23 (100%) PHPCBF RESULT SUMMARY----------------------------------------------------------------------------------------------FILE FIXED REMAINING----------------------------------------------------------------------------------------------/Users/paul/code/sandbox/phpcs-part-1/app/Models/User.php 1 0/Users/paul/code/sandbox/phpcs-part-1/app/Http/Controllers/Controller.php 1 0----------------------------------------------------------------------------------------------A TOTAL OF 2 ERRORS WERE FIXED IN 2 FILES----------------------------------------------------------------------------------------------
Conclusion
We covered quite a bit of ground, but you should be able to start using PHP Codesniffer from scratch with Laravel. It can be an excellent addition if you want to target some of the specific rules that PHP CS Fixer doesn't perform, and I've equipped you with how to disable conflicting rules or rules that you want to disable.