PHPMD (PHP CLI)
Goal
PHPMD reads PHP against a fixed set of rules and reports the shape problems a
standards checker does not look for — a method with too many lines or too many
branches, a class with too many dependencies, a public property that could be
private. codesize measures size and complexity; design measures coupling and
class shape. With it installed, a claim that a class or method in a PHP CLI
project's library has grown too large to review can be checked rather than
asserted.
Install
composer require --dev phpmd/phpmd
PHPMD registers no Composer plugin, so there is no allow-plugins step and no
separate registration command — the package is ready to run once required.
PHPMD's latest release, 2.15.0 (2023-12-11), runs on PHP 8.3 but its parser
cannot read PHP 8.4 syntax it has never had to handle: new without
parentheses, asymmetric visibility (protected(set)), and property hooks each
make it error out on the file rather than report on it (phpmd/phpmd issues
1219, #1237, #1271). A scan over code using any of them fails, not a clean
report. The fix lives only on the unreleased 3.x branch, whose CLI drops the
positional phpmd <paths> <format> <ruleset> form for named flags —
phpmd analyze <paths> --format <format> --ruleset <ruleset> — so the text
example below changes once 3.0 ships.
Run
vendor/bin/phpmd --version
Run the binary directly, not through php: a missing vendor/bin/phpmd
then exits 127, the exit code that says a tool is absent rather than merely
failing.
PHPMD's own usage is phpmd <paths> <report-format> <ruleset(s)>, all three
positional and required, so a bare --version is the only invocation that proves
the tool is present without also naming a scope:
php vendor/bin/phpmd src text codesize,design
A library kept to plain .php files needs no --suffixes flag, since that is
PHPMD's default; a project that commits a phpmd.xml names it in place of the two
shipped rulesets. Findings print to stdout in the chosen format. PHPMD 2.15.0
exits 2 when it reports a violation, 1 when it cannot run at all (a path that does
not exist), and 0 when clean — both non-zero exits are findings, not one success
and one failure.
PHPMD skips a file whose extension is not in its list even when the file is named
directly on the command line, so it never reaches an extensionless Composer binary
under bin/ — that file is linted and analysed by hand or by a glob-capable pass
instead.
If vendor/bin/phpmd is absent, the shell exits 127: install, then run it
again.