My code quality setup

When building a codebase, either on your own or within a team, its a good idea to keep code consistant. One of the main tools to help keep code consistant is to decide on and employ coding standards. Yes, that means tabs vs spaces, but it also means decisions such as trailing commas, unwanted whitespace and bracket placement.

Trying to remember all of these rules is impossible. Although you will become accustomed to some of the rules through practice and repetition, computers are much better at this task. There are different tools which can help automate checking and fixing coding standard discrepancies.

I'm going to talk about the tools I use to help improve code quality for PHP. There are similar tools for other programming languages, such as the JavaScript Standard Style.


PHP CodeSniffer

PHP CodeSniffer is probably the most well-known tool. This will check against a defined set of rules – you can configure these rules to suit your requirements using phpcs.xml.

Depending on how you installed the tool, you will likely run the command to check your code by running the following;

phpcs /app
phpcs --standard=PSR12 /app

I install PHP CodeSniffer using Composer;

composer require --dev squizlabs/php_codesniffer

Then use the locally installed path;

./vendor/bin/phpcs /app
./vendor/bin/phpcs --standard=PSR12 /app

The phpcs.xml file allows for a lot of configuration to help fit with your project and requirements. I use Slevomat Coding Standard and ImportDetection as additional standards to follow. I have included an example of how I use these at the bottom of the article1.


PHPStan

PHPStan is a static-analysis tool which helps you find bugs and unused code without writing tests. I use Larastan which has some sensible configuration defaults for Laravel applications. I install this tool using Composer;

composer require --dev nunomaduro/larastan

After configuring the rules you need using phpstan.neon you can check your codebase by running the following;

./vendor/bin/phpstan analyse

If you're adding PHPStan to an existing codebase, the errors maybe overwhelming. You can generate a baseline, which means these errors are suppressed, but new code will follow the standard.

./vendor/bin/phpstan analyse --generate-baseline

Other tools

Mess Detector is another useful tool that helps find suboptimal code, overcomplicated expressions and unused parameters, methods, properties.

This can be installed using Composer;

composer require --dev phpmd/phpmd

You can configure rules using phpmd.xml – I have included an example at the bottom2. You run the tool using the following command;

php ./vendor/bin/phpmd ./src text phpmd.xml

Copy/paste detector helps find duplicate code. Although the author recommends not installing this tool using Composer, I don't agree with his reasoning and install it using the following;

composer require --dev sebastian/phpcpd

And you run the tool with the following command;

php ./vendor/bin/phpcpd ./app 

Standardising these commands

It can be difficult to remember the exact commands and arguments you need to write to run each of these commands. This is why I recommend adding scripts to the composer.json file. These then act as shortcuts to the commands you need, with the arguments you want.

Below are the scripts I have for the coding standards tools described above;

"scripts": {
    "sniff": [
        "php -d memory_limit=-1 ./vendor/bin/phpcs -s"
    ],
    "stan": [
        "php -d memory_limit=-1 ./vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=2G"
    ],
    "cpd": [
        "php ./vendor/bin/phpcpd ./src --log-pmd ./code-analysis/phpcpd.xml"
    ],
    "mess": [
        "php ./vendor/bin/phpmd ./src text phpmd.xml --reportfile ./code-analysis/phpmd.txt"
    ]
}

This means I can run the following consistant and easy to remember commands and everything is tested as I need it to be.

composer run sniff
composer run stan
composer run mess
composer run cpd

As you can see from the script commands, I am using additional arguments. I am changing the PHP memory limit by prefixing commands with php -d memory_limit=-1. I am also enabling reporting for phpcpd and phpmd commands, which point to specific output files inside my git-ignored /code-analysis/ folder.

You can still pass additional arguments if you need one-off changes;

composer run stan -- --generate-baseline

#1. Example phpcs.xml

<?xml version="1.0" encoding="UTF-8"?>
<ruleset>
    <description>Code Sniffer, including PSR, Slevomat Coding Standard and Import Detection.</description>
    <config name="installed_paths" value="vendor/slevomat/coding-standard,vendor/sirbrillig/phpcs-import-detection"/>

    <file>src/</file>
    <file>config/</file>
    <file>database/</file>
    <file>tests/</file>

    <exclude-pattern>*/cache/*</exclude-pattern>
    <exclude-pattern>*/*.js</exclude-pattern>
    <exclude-pattern>*/*.css</exclude-pattern>
    <exclude-pattern>*/*.xml</exclude-pattern>
    <exclude-pattern>*/autoload.php</exclude-pattern>
    <exclude-pattern>*/storage/*</exclude-pattern>
    <exclude-pattern>*/vendor/*</exclude-pattern>

    <arg name="colors"/>
    <arg value="sp"/>

    <ini name="memory_limit" value="256M"/>

    <rule ref="Generic.PHP.DeprecatedFunctions"/>
    <rule ref="Generic.PHP.ForbiddenFunctions"/>
    <rule ref="Generic.Classes.DuplicateClassName"/>

    <rule ref="PSR1"></rule>
    <rule ref="PSR2"></rule>
    <rule ref="PSR12"></rule>
    <rule ref="SlevomatCodingStandard"></rule>

    <rule ref="ImportDetection">
        <properties>
            <property name="ignoreUnimportedSymbols" value="/^(env|value|url|optional)$/"/>
        </properties>
    </rule>

    <rule ref="Generic.Files.LineLength">
        <properties>
            <property name="lineLimit" value="500"/>
            <property name="absoluteLineLimit" value="0"/>
        </properties>
    </rule>
</ruleset>

#2. Example phpmd.xml

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="phpmd">
    <description>PHP Mess Detector Ruleset</description>
    <rule ref="rulesets/codesize.xml"/>
    <rule ref="rulesets/design.xml"/>
    <rule ref="rulesets/naming.xml">
        <exclude name="ShortVariable"/>
        <exclude name="LongVariable"/>
    </rule>
    <rule ref="rulesets/unusedcode.xml"/>
</ruleset>