A Set of Static Analyzers for PHP Code

A static analyzer scans the source code for errors and vulnerabilities and for compliance with the coding standard.

The article describes how to install, configure and run static analyzers:

PHPStan

Installation

composer require --dev phpstan/phpstan

Run

vendor/bin/phpstan analyze -l 9 src

Psalm

Installation

composer require --dev vimeo/psalm

Configuration

src/psalm.xml
<?xml version="1.0"?>
<psalm
    errorLevel="7"
    resolveFromConfigFile="true"
    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"
    findUnusedBaselineEntry="true"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="vendor" />
        </ignoreFiles>
    </projectFiles>

    <issueHandlers>
        <PropertyNotSetInConstructor>
            <errorLevel type="suppress">
                <directory name="src" />
            </errorLevel>
        </PropertyNotSetInConstructor>
    </issueHandlers>
</psalm>

Run

vendor/bin/psalm -c psalm.xml --show-info=true

PHP_CodeSniffer

Installation

composer require --dev squizlabs/php_codesniffer
composer require --dev slevomat/coding-standard

Configuration

src/phpcs_ruleset.xml
<?xml version="1.0"?>
<ruleset name="App">
    <rule ref="vendor/slevomat/coding-standard/SlevomatCodingStandard/ruleset.xml">
        <exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes.IncorrectStrictTypesFormat"/>
        <exclude name="SlevomatCodingStandard.Namespaces.UseOnlyWhitelistedNamespaces.NonFullyQualified"/>
        <exclude name="SlevomatCodingStandard.Attributes.AttributesOrder"/>
        <exclude name="SlevomatCodingStandard.Files.TypeNameMatchesFileName.NoMatchBetweenTypeNameAndFileName"/>
        <exclude name="SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces.NoEmptyLineAfterOpeningBrace"/>
        <exclude name="SlevomatCodingStandard.Functions.DisallowNamedArguments.DisallowedNamedArgument"/>
        <exclude name="SlevomatCodingStandard.Functions.RequireTrailingCommaInCall.MissingTrailingComma"/>
        <exclude name="SlevomatCodingStandard.Attributes.AttributeAndTargetSpacing.IncorrectLinesCountBetweenAttributeAndTarget"/>
        <exclude name="SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces.NoEmptyLineBeforeClosingBrace"/>
        <exclude name="SlevomatCodingStandard.Functions.FunctionLength.FunctionLength"/>
        <exclude name="SlevomatCodingStandard.Files.FunctionLength.FunctionLength"/>
        <exclude name="SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.DisallowNullSafeObjectOperator.DisallowedNullSafeObjectOperator"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.NewWithoutParentheses.UselessParentheses"/>
        <exclude name="SlevomatCodingStandard.Functions.RequireTrailingCommaInDeclaration.MissingTrailingComma"/>
        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedExceptions.NonFullyQualifiedException"/>
        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions.NonFullyQualified"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.DisallowEmpty.DisallowedEmpty"/>
        <exclude name="SlevomatCodingStandard.Classes.DisallowConstructorPropertyPromotion.DisallowedConstructorPropertyPromotion"/>
        <exclude name="SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint"/>
        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalConstants.NonFullyQualified"/>
        <exclude name="SlevomatCodingStandard.Exceptions.DisallowNonCapturingCatch.DisallowedNonCapturingCatch"/>
        <exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation.NonFullyQualifiedClassName"/>
        <exclude name="SlevomatCodingStandard.PHP.RequireExplicitAssertion.RequiredExplicitAssertion"/>
        <exclude name="SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment.OneLinePropertyComment"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.RequireMultiLineTernaryOperator.MultiLineTernaryOperatorNotUsed"/>
        <exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"/>
        <exclude name="SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.DisallowYodaComparison.DisallowedYodaComparison"/>
        <exclude name="SlevomatCodingStandard.Classes.TraitUseSpacing.IncorrectLinesCountBeforeFirstUse"/>
        <exclude name="SlevomatCodingStandard.PHP.DisallowReference.DisallowedPassingByReference"/>
        <exclude name="SlevomatCodingStandard.Arrays.AlphabeticallySortedByKeys.IncorrectKeyOrder"/>
        <exclude name="SlevomatCodingStandard.Classes.TraitUseSpacing.IncorrectLinesCountAfterLastUse"/>
        <exclude name="SlevomatCodingStandard.Commenting.RequireOneLineDocComment.MultiLineDocComment"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.RequireYodaComparison"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.BlockControlStructureSpacing.IncorrectLinesCountAfterControlStructure"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.JumpStatementsSpacing.IncorrectLinesCountBeforeControlStructure"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.BlockControlStructureSpacing.IncorrectLinesCountBeforeControlStructure"/>
        <exclude name="SlevomatCodingStandard.Functions.DisallowArrowFunction.DisallowedArrowFunction"/>
        <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/>
        <exclude name="SlevomatCodingStandard.Arrays.TrailingArrayComma.MissingTrailingComma"/>
        <exclude name="SlevomatCodingStandard.ControlStructures.UselessIfConditionWithReturn.UselessIfCondition"/>
    </rule>
</ruleset>

Run

vendor/bin/phpcs -s --standard=phpcs_ruleset.xml src

PHP Coding Standards Fixer

Installation

composer require --dev friendsofphp/php-cs-fixer

Run

vendor/bin/php-cs-fixer fix --dry-run --diff -v src

PHPMD

Installation

composer require --dev phpmd/phpmd

Configuration

src/phpmd_ruleset.xml
<?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></description>

    <rule ref="rulesets/cleancode.xml">
        <exclude name="StaticAccess" />
        <exclude name="BooleanArgumentFlag" />
        <exclude name="ElseExpression" />
    </rule>

    <rule ref="rulesets/design.xml">
        <exclude name="CouplingBetweenObjects" />
        <exclude name="EmptyCatchBlock" />
    </rule>

    <rule ref="rulesets/naming.xml">
        <exclude name="ShortVariable" />
        <exclude name="LongVariable" />
        <exclude name="LongClassName" />
    </rule>

    <rule ref="rulesets/unusedcode.xml">
        <exclude name="UnusedFormalParameter" />
    </rule>
</ruleset>

Run

# apk add jq
# or
# apt-get install jq
vendor/bin/phpmd src json phpmd_ruleset.xml | jq