PHP Coding standard tools and configuration

0_Ag4xabqSJTs5i5BV.jpg

As we all know by now, applications evolve a lot during their lifetime and so does the team that works on those applications. There are always people that leave a team to transition to another team or leave the company as there are also new joiners to a team.

It’s a continuous struggle to keep up with the same approach, paradigms, or coding styles in any programming language and application with so many changes in the team or application itself.

Thankfully we have the so-called, coding standard tools, at our disposal as developers to help us mitigate these issues and make sure that everyone is on the same page and adheres to the defined rules when writing new code to extend the application and reading old code when something has to be changed.

For PHP, these tools are:

  • PHP Mess Detector (PHPMD)
  • PHP Code Sniffer (PHPCS)
  • PHP Code Sniffer Fixer (PHPCBF)
  • PHPSTAN
  • PSALM
  1. PHP Mess Detector (PHPMD)

PHPMD makes sure that your code follows the SOLID principles, adheres to the software design patterns, follows the naming conventions, and checks for unused code. This is all done by the list of rules the PHPMD has and those rules are grouped into 6 rulesets:

You can find more about each of these groups and the rules that they have by visiting their respective documentation. Below you’ll find my most used configuration for this tool with 2 examples of how to override rules, specifically for naming to ignore the $id property of a class or database model or i,j loop variables and how to ignore a static call for a class if there is no other way how to call that class.

                <?xml version="1.0"?>
<ruleset name="PHPMD rule set"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <description>
        Rule set that checks the code against the specified rules to avoid unnecessary complexity
    </description>

    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/codesize.xml" />

    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/design.xml" />

    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/naming.xml" >
      <exclude name="ShortVariable"/>
    </rule>
    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/naming.xml/ShortVariable">
        <properties>
            <property name="exceptions" value="id,q,i,j" />
        </properties>
    </rule>

    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/unusedcode.xml" />

    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/unusedcode.xml" />

    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/cleancode.xml">
        <exclude name="StaticAccess" />
    </rule>
    <rule ref="vendor/phpmd/phpmd/src/main/resources/rulesets/cleancode.xml/StaticAccess">
        <properties>
            <property name="exceptions">
                <value>
                    \libphonenumber\PhoneNumberUtil
                </value>
            </property>
        </properties>
    </rule>
</ruleset>
            

2. PHP Code Sniffer (PHPCS)

This tool is used to detect code violations based on a predefined set of rules, like, for example, forbidding the use of certain functions like var_dump, delete, extract, sizeof, etc. Standardize the usage of single or double quotes, type-hinting, doc block, spacing, forbidden annotations, etc.

Personally, I prefer to use this tool in combination with https://github.com/slevomat/coding-standard and that’s where the configuration below also relies on.

                <?xml version="1.0" encoding="UTF-8"?>

<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">

    <description>Check the code of the sniffs.</description>
    <arg name="basepath" value="."/>
    <arg name="cache" value=".phpcs-cache"/>
    <arg name="colors"/>
    <arg name="extensions" value="php"/>
    <config name="installed_paths" value="vendor/slevomat/coding-standard/"/>
    <rule ref="PSR12">
        <exclude name="Generic.Files.LineLength"/>
    </rule>
    <rule ref="Generic.Arrays.DisallowLongArraySyntax.Found">
        <type>warning</type>
    </rule>
    <rule ref="Generic.PHP.DeprecatedFunctions"/>
    <rule ref="Generic.PHP.ForbiddenFunctions">
        <properties>
            <property name="forbiddenFunctions" type="array">
                <element key="chop" value="rtrim"/>
                <element key="close" value="closedir"/>
                <element key="compact" value="null"/>
                <element key="delete" value="unset"/>
                <element key="doubleval" value="floatval"/>
                <element key="echo" value="null"/>
                <element key="extract" value="null"/>
                <element key="fputs" value="fwrite"/>
                <element key="ini_alter" value="ini_set"/>
                <element key="is_double" value="is_float"/>
                <element key="is_integer" value="is_int"/>
                <element key="is_long" value="is_int"/>
                <!--<element key="is_null" value="null"/>-->
                <element key="is_real" value="is_float"/>
                <element key="is_writeable" value="is_writable"/>
                <element key="join" value="implode"/>
                <element key="key_exists" value="array_key_exists"/>
                <element key="pos" value="current"/>
                <element key="settype" value="null"/>
                <element key="show_source" value="highlight_file"/>
                <element key="sizeof" value="count"/>
                <element key="strchr" value="strstr"/>
                <element key="var_dump" value="null"/>
            </property>
        </properties>
    </rule>
    <rule ref="Squiz.Strings.ConcatenationSpacing">
        <properties>
            <property name="spacing" value="1"/>
        </properties>
    </rule>
    <rule ref="Squiz.Strings.DoubleQuoteUsage"/>
    <rule ref="SlevomatCodingStandard.Classes.ClassStructure"/>
    <rule ref="SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces">
        <properties>
            <property name="linesCountAfterOpeningBrace" value="0"/>
            <property name="linesCountBeforeClosingBrace" value="0"/>
        </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration"/>
    <rule ref="SlevomatCodingStandard.Commenting.UselessFunctionDocComment"/>
    <rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses"/>
    <rule ref="SlevomatCodingStandard.Namespaces.DisallowGroupUse"/>
    <rule ref="SlevomatCodingStandard.Namespaces.MultipleUsesPerLine"/>
    <rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
    <rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
        <properties>
            <property name="searchAnnotations" value="true"/>
        </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash"/>
    <rule ref="SlevomatCodingStandard.TypeHints.LongTypeHints"/>
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing"/>
    <rule ref="Squiz.WhiteSpace.FunctionSpacing">
        <properties>
            <property name="spacing" value="1"/>
            <property name="spacingBeforeFirst" value="0"/>
            <property name="spacingAfterLast" value="0"/>
        </properties>
    </rule>
    <rule ref="Squiz.WhiteSpace.FunctionOpeningBraceSpace" />
    <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
        <properties>
            <!-- turned on by PSR2 -> turning back off -->
            <property name="ignoreBlankLines" value="false"/>
        </properties>
    </rule>
    <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace.EmptyLines">
        <!-- turned off by PSR2 -> turning back on -->
        <severity>5</severity>
    </rule>
    <rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
        <properties>
            <property name="forbiddenAnnotations" type="array">
                <element value="@api"/>
                <element value="@author"/>
                <element value="@category"/>
                <element value="@copyright"/>
                <element value="@covers"/>
                <element value="@coversDefaultClass"/>
                <element value="@coversNothing"/>
                <element value="@created"/>
                <element value="@license"/>
                <element value="@package"/>
                <element value="@since"/>
                <element value="@subpackage"/>
                <element value="@version"/>
            </property>
        </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.Exceptions.DeadCatch"/>
    <rule ref="SlevomatCodingStandard.ControlStructures.AssignmentInCondition"/>

    <file>config/</file>
    <file>public/</file>
    <file>src/</file>
    <file>tests/</file>
    <exclude-pattern type="relative">*.(js|css)</exclude-pattern>

</ruleset>
            

3. PHP Code Sniffer Fixer (PHPCBF)

PHPCBF is an addition to PHPCS. What it does is that it tries to fix as many of the reported issues as possible. I’m emphasizing the as many as possible here since PHPCBF cannot fix all of the reported issues. It fixes the simple line code formatting in regards to spacing, quotes format, and some minor code changes on some conditions, using fully qualified names for your imports, but it doesn’t automatically correct everything for you.

4. PHPSTAN

This tool makes sure that your code is properly annotated and that it has the correct return types. It makes it a lot easier to work with the code base, not only for the automated tools and tests but also for other technical or non-technical people who work with the code. Here is my configuration with a custom rule for this tool.

                <?php

namespace App\PHPStanRule;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;

class MyRule implements Rule
{
    private bool $reportMaybes;

    public function __construct(bool $reportMaybes = false)
    {
        $this->reportMaybes = $reportMaybes;
    }

    public function getNodeType(): string
    {
        return Node::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        var_dump($this->reportMaybes);
        //var_dump(get_class($node));
        return [];
    }

}
            
                parameters:
    ignoreErrors:
      -
	message: "#^Only numeric types are allowed in pre\\-decrement, bool\\|float\\|int\\|string\\|null given\\.$#"
	count: 1
	path: src/Analyser/Scope.php
      -
	message: "#^Anonymous function has an unused use \\$container\\.$#"
	count: 2
	path: src/Command/CommandHelper.php
            
                includes:
   - phpstan-baseline.neon

rules:
    - App\PHPStanRule\MyRule

parameters:
	level: max
	paths:
		- src
		- tests
	scanDirectories:
	    - src
	    - tests
	reportUnmatchedIgnoredErrors: false
	ignoreErrors:
		- '#Call to an undefined method [a-zA-Z0-9\\_]+::doFoo\(\)#'
	excludePaths:
	    analyse:
		- src/thirdparty
	    analyseAndScan:
		- src/broken
	parallel:
	    processTimeout: 120.0

services:
     -
	class: App\PHPStanRule\MyRule
	arguments:
		reportMaybes: true
	tags:
		- phpstan.rules.rule
            

5. PSALM

Psalm is also a static analysis tool like PHPSTAN but PASLM attempts to dig into your program and find many more issues than PHPSTAN. It has a few features that go further than other similar tools:

  • Mixed type warnings
    This means that we cannot use mixed as type-hint or return type but PHPSTAN allows this.
  • Intelligent logic checks
    Keeps track of logical assertions made about your code, so if ($a && $a) {} and if ($a && !$a) {} are both treated as issues. Checks also logical assertions made in prior code paths, preventing issues like if ($a) {} elseif ($a) {}.
  • Property initialization checks
    Makes sure that all properties of a given object have values after the constructor is called.
  • Taint analysis
    PSALM can detect security vulnerabilities in your code.
  • Automatic fixes and refactoring
    PSALM can fix many of the issues it finds automatically and perform simple refactors from the command line.

Here is my basic PSALM configuration.

                <?xml version="1.0"?>
<psalm
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="https://getpsalm.org/schema/config"
        xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
        reportMixedIssues="false"
>
    <projectFiles>
        <directory name="src"/>
        <directory name="tests"/>
        <ignoreFiles>
            <directory name="vendor"/>
        </ignoreFiles>
    </projectFiles>
    <issueHandlers>
        <PropertyNotSetInConstructor>
            <errorLevel type="suppress">
                <directory name="tests/"/>
                <referencedProperty name="$backupStaticAttributes"/>
                <referencedProperty name="$runTestInSeparateProcess"/>
            </errorLevel>
        </PropertyNotSetInConstructor>
        <UnusedClass>
            <errorLevel type="suppress">
                <directory name="tests/"/>
            </errorLevel>
        </UnusedClass>
    </issueHandlers>
</psalm>
            
                NOTE: Personally I like to use a combination of PHPSTAN and PSALM on my projects since they check different aspects of your codebase, even though the seem quite similar
            

Hope you find this article useful and that it can help you in your day-to-day work. Please follow and subscribe for more articles like this.


Only registered users can post comments. Please, login or signup.

Start blogging about your favorite technologies and get more readers

Join other developers and claim your FAUN account now!

Avatar

Albion Bame

Staff Software Engineer, Emma - The Sleep Company

@abame
Howdy, I’m Albion, I’m a web developer living in Frankfurt am Main, Germany, originally from Albania, fan of DIY, cycling, and camping. I’m also interested in travel and reading.
Stats
23

Influence

983

Total Hits

4

Posts