The PHP Mess Detector just shipped PHPMD 3.0.0, its first release since 2.15.0 in December 2023. It raises the minimum PHP version from 5.3.9 to 8.1 and replaces the command line interface. It also adds a new way to configure and suppress rules.

  • A #[SuppressWarnings] attribute replaces the docblock annotation
  • A new UnusedSuppression rule finds suppressions that no longer do anything
  • Configuration in YAML, JSON, or PHP, with phpmd init and phpmd migrate commands
  • A --threads option for parsing files in parallel
  • Two new rules, IfStatementWithoutLogic and LongMethodName
  • A renderer for GitHub Check Runs

What's New

Suppressing Warnings With an Attribute

PHPMD 2 used a @SuppressWarnings docblock annotation. PHPMD 3 adds a PHP attribute that takes the rule's class name:

use PHPMD\Attribute\SuppressWarnings;
use PHPMD\Rule\UnusedLocalVariable;

#[SuppressWarnings(UnusedLocalVariable::class)]
public function example() {
    $unused = 42;
}

Because the rule is a class reference, your editor can autocomplete it and catch a typo. The docblock annotation still works, but it's now deprecated.

The new UnusedSuppression rule reports #[SuppressWarnings] attributes that no longer suppress a warning. That catches the suppression someone added years ago for code that has since changed. It only checks attributes, not docblock annotations. The upgrade guide suggests adding existing reports to a baseline with --generate-baseline if you want to adopt it gradually.

Issue: #1230

New Configuration Formats and a Setup Wizard

Rule sets can now be written in YAML, JSON, or PHP as well as XML, and YAML is the recommended format. PHPMD looks for a file like phpmd.yml, phpmd.json, or phpmd.xml in the current directory, so you don't have to pass a rule set on every run.

The config file can also hold settings that used to be CLI options, including paths, the output format, cache settings, the baseline file, and the thread count.

Two new commands help you get there:

  • phpmd init builds a phpmd.yml through an interactive wizard
  • phpmd migrate converts a PHPMD 2 config to YAML, renaming the rule classes and threshold properties that changed

phpmd migrate keeps the original file and lists every change it made. Add --dry-run to print the result without writing it.

Parallel Parsing and a Progress Bar

The new --threads option parses files in parallel, which helps on large codebases. PHPMD also shows a progress bar on stderr during analysis. Use --no-progress to hide it in CI.

New Rules and Options

IfStatementWithoutLogic flags conditions that contain only literals, like if (true). LongMethodName flags method and function names over a set length.

A few existing rules got smarter too. UnusedFormalParameter now skips methods marked with #[Override], since those parameters come from the parent signature. StaticAccess ignores built-in enum methods like from() and cases(). UnusedPrivateField, UnusedFormalParameter, and ExcessiveParameterList accept an exceptions list to skip named methods.

GitHub Check Runs Output

A new renderer outputs results in the GitHub Check Runs format, so violations can show up as annotations on a pull request.

PR: #965

Upgrade Notes

PHPMD 3 has breaking changes, and the CLI change will affect most CI scripts. The upgrade guide covers each one.

PHP 8.1 or newer is required.

The command signature changed. PHPMD now uses Symfony Console, and the format and rule set are options instead of positional arguments:

# PHPMD 2
phpmd src/ text codesize,unusedcode

# PHPMD 3
phpmd analyze --ruleset codesize --ruleset unusedcode src/

Several options were renamed. --ignore is now --exclude, --extensions is now --suffixes, and --reportfile is split into --reportfile-text, --reportfile-xml, and so on.

Thresholds now mean "highest allowed value." Size and complexity rules use a single maximum property, and they only report when a value goes over it. In PHPMD 2, many of them also reported when the value equaled the threshold. With the same config, you may see fewer warnings after upgrading. phpmd migrate --preserve-behavior lowers those thresholds by one to keep the old results. The old property names still work as aliases.

A new exit code. Files that fail to process now exit with code 3 instead of 1. Check any CI step that looks for specific exit codes.

Baseline updates report new violations. --update-baseline used to drop violations that weren't in the baseline. It now reports them and exits with code 2.

Custom rule authors should also check the guide. Several rule classes were renamed, and suppressions are now applied after rules run, so custom rules no longer need to check for them.

Install

composer require --dev phpmd/phpmd:^3.0

References