In this post Steve Boyd demonstrates how to convert a SilverStripe 3.x codebase to the PSR-2 coding standard and create a git pre-commit hook to ensure that all future commits adhere to PSR-2.
What is PSR-2 and why do we want it?
Adopting coding standards makes it easier for teams to work together. There is joint consensus for how code should be formatted, making it easier for others to peer review the code we write.
PSR-2 is the PHP coding standard recommended by PHP-FIG, a group dedicated to addressing the commonalities and standardisation of PHP projects, making it the most common PHP coding standard. You can read about PSR-2 in detail here.
We've also implemented PSR-2 as a standard in SilverStripe 4, so getting into it now will save you some pain when upgrading your code later.
Easy autoformating
Because of the standards popularity, there is great auto-formatting support available in a wide range of IDEs (Integrated development environments). This also means that it's easy to get a new team to agree to use these coding standards when starting a new project.
Thanks to the abundance of automated code formatting tools, you don't even need to write PSR-2 compliant code yourself. You simply write code however you feel comfortable, press a couple of keys, and revel in the joy of your IDE automatically formatting your code for you.
Slightly looser PSR-2 ruleset
SilverStripe 3.x used to use some old conventions, and they've recently been switching over to using PSR-2. From SilverStripe 3.5 onwards you'll likely have a phpcs.xml.dist
ruleset that ships with your installer, which will contain the examples we're going to run through in detail below.
Note that while PSR-2 hasn't been implemented throughout the core SilverStripe 3 codebase, it has been in SilverStripe 4. If you implement PSR-2 in your own project code now you'll ensure the transition to SilverStripe 4 is made easier by removing this difference as early as possible.
Our customised PSR-2 ruleset is going to ignore a couple of rules:
- Exclude camelcaps methods - (so that you can still use 'hardcoded' functions like
Controller::Form()
) - Excludes camelcaps classes - (so that you can use the conventional controller functions like
Page_Controller
)
There are also a couple of rules that you can optionally ignore because of how most SilverStripe 3.x codebases are:
- Exclude only one class per file - it used to be a SilverStripe 3 coding standard to include multiple classes per file, e.g.
Page
andPage_Controller
, and while we're now aiming to follow PSR-2 in SilverStripe 3 it isn't implemented everywhere yet - Exclude mandatory namespace
Using PHPCS + PHPCBF to convert the codebase to PSR-2
PHPCS and PHPCBF are a pair of common command-line tools used to analyse and autoformat PHP code.
Add the file phpcs.xml
to the root of your project, or modify the existing phpcs.xml.dist
file if you have one already (note that a phpcs.xml
file will take priority over a phpcs.xml.dist
file).
phpcs.xml
<?xml version="1.0"?>
<ruleset name="PSR2-SS3">
<description>A modified version of PSR2 that excludes some rules for SilverStripe 3.x</description>
<!-- Include the whole PSR-2 standard -->
<rule ref="PSR2">
<!--
- Exclude mandatory namespace
- Exclude only one class per file
-->
<exclude name="PSR1.Classes.ClassDeclaration"/>
<!--
- Exclude camel caps methods - e.g. Controller::Form()
-->
<exclude name="PSR1.Methods.CamelCapsMethodName"/>
<!--
- Excludes camel caps classes - e.g. Page_Controller
-->
<exclude name="Squiz.Classes.ValidClassName"/>
</rule>
</ruleset>
Update your composer.json
file, adding "squizlabs/php_codesniffer"
in "require-dev"
.
This will install the phpcs
and phpcbf
binaries in ./vendor/bin
.
composer.json
{
...
"require-dev": {
"squizlabs/php_codesniffer": "^3"
},
...
}
Run composer install
to install CodeSniffer.
Showing and fixing errors
Run the following to display errors in a directory:
./vendor/bin/phpcs --colors --extensions=php -n -p [directory]
You'll only want to do this on files for your application, not third-party directories installed by composer. In silverstripe 3.x, this will usually be:
./vendor/bin/phpcs --colors --extensions=php -n -p mysite
From there you can can choose to fix the errors yourself, or let PHP Code Beautifier and Fixer (phpcbf) to do it for you. The syntax is the same as phpcs:
./vendor/bin/phpcbf --colors --extensions=php -n -p mysite
Then run phpcs again to check that all errors were automatically fixed. You may need to manually fix some errors that phpcbf cannot automatically fix.
Commit your files and you're done!
Because this is going to change a large number of files, I highly recommend you don't do any other feature/bugfix/refactor development, as the git diff is going to be potentially huge.
Creating a git pre-commit hook
You can maintain PSR-2 standards within your team by using a git pre-commit hook. This ensures that all php code meets the PSR-2 standard before being committed.
Update your composer.json
file, add "scripts"
- "post-install-cmd"
- "php composer-script.php"
. What this will do is run the file composer-script.php
when you run composer install
.
composer.json
{
...
"scripts": {
"post-install-cmd": [
"php composer-script.php"
]
}
...
}
The function of composer-script.php
is to create or update the file ./.git/hooks/pre-commit
and copy the contents of the file pre-commit.sh
in there.
composer-script.php
<?php
// This script will copy a pre-commit hook from this repo to a developers local git
// The pre-commit hook will be run whenever a developer runs "git commit"
// This script will run after running composer install, via the "post-install-cmd" in composer.json
$gitHookFilename = '.git/hooks/pre-commit';
$shellHookFilename = 'pre-commit.sh';
if (file_exists($gitHookFilename)) {
unlink($gitHookFilename);
}
copy($shellHookFilename, $gitHookFilename);
chmod($gitHookFilename, 0744);
pre-commit.sh
#!/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 (php -l) ..."
for FILE in $SFILES
do
php -l -d display_errors=1 $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 (phpcs) ..."
./vendor/bin/phpcs --colors -n -p $FILES
if [ $? != 0 ]
then
echo "Fix the error before commit."
echo "You can do this automatically with"
echo "./vendor/bin/phpcbf [FILE]"
exit 1
fi
fi
exit $?
It runs when you git commit
The git pre commit hook runs when a developer runs the command git commit
.
The file pre-commit.sh
will run php lint and phpcs to ensure that the committed *.php files meet the PSR-2 code standard. It will automatically use the phpcs.xml
file in the root directory.
Files that do not meet the standard will be displayed in the terminal output with the part of the code that is failing. No files will be committed.
Once these files have been fixed to pass the PSR-2 standard then they'll be allowed to be committed.
One last thing; if you do this on a new repository then you'll need to have have at least one commit so that HEAD revision exists in git before this pre-commit script works properly.
Happy coding!
If you've enjoyed this blog post, there's a similar blog post for JavaScript linting which you could add to your project.
If you want to keep updated on what is happening in the SilverStripe Community, join the Community slack channel!
Post your comment
Comments
No one has commented on this page yet.
RSS feed for comments on this page | RSS feed for all comments