# PHPMD is a mess detector that reads PHP Depend's metrics and prints design smells

> A rule-based static analyser for PHP that reports coupling, complexity and naming problems rather than type errors, with baselines and suppression attributes for adopting it on an existing codebase.

**phpmd/phpmd** — PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD. PHPMD can be seen as an user friendly frontend application for the raw metrics stream measured by PHP Depend.

- Repository: https://github.com/phpmd/phpmd
- Website: https://phpmd.org
- Stars: 2,461 · Forks: 361
- Language: PHP
- License: BSD-3-Clause
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/phpmd-phpmd

## A frontend for metrics another project measures

The project's own description is the clearest statement of what PHPMD is and is not: it is a spin-off of PHP Depend, aiming to be a PHP equivalent of the Java tool PMD, and it can be seen as a user friendly frontend application for the raw metrics stream measured by PHP Depend.

That division of labour is the whole architecture. PHP Depend parses the source and produces metrics. PHPMD holds rules that decide which metric values are interesting and a rendering layer that decides how to present them. So PHPMD has no parser of its own worth speaking of, which also means its rules operate on numbers and structure rather than on text patterns.

The practical consequence is that PHPMD and PHPStan do not compete. One asks whether the code is well shaped; the other asks whether it is well typed. The repository itself demonstrates the pairing, since the tree at the root includes a `phpstan.neon` and a `phpcs.xml.dist`, and release 2.14.0 added PHPStan to CI. A project that runs PHPMD on itself also runs static analysis and a coding standard checker alongside it, which is a reasonable summary of where it fits in a pipeline.

The homepage is phpmd.org, the licence is BSD-3-Clause, and the topics on GitHub include clean-code, static-analysis and mess-detector.

## Command line usage and automatic ruleset discovery

The whole command line surface starts with `phpmd analyze`, and the README's example is short enough to type from memory:

```bash
phpmd analyze src/
```

You pass paths, comma-separated lists of files, or a directory. What PHPMD does with those paths depends on a ruleset, and the discovery behaviour here is unusually well thought out. If you do not pass `--ruleset`, it looks in the current directory for a configuration file and accepts any of `phpmd.yml`, `phpmd.yaml`, `phpmd.json`, `phpmd.xml` or `phpmd.php`, in that order of priority, including the dot-prefixed and `.dist`-suffixed variants such as `.phpmd.yml` or `phpmd.yml.dist`. So a project that prefers `phpmd.yaml.dist` for version control still works without extra arguments.

If you would rather not write one, `phpmd init` generates a `phpmd.yml` through an interactive wizard, and `phpmd migrate` upgrades a configuration file written for PHPMD 2. That second command matters more than it looks given the branch situation below.

There is also a shortcut for the common case, since built-in rulesets have short names:

```bash
phpmd analyze --ruleset codesize src/
```

One detail worth knowing before you debug a missing ruleset: the Phar distribution carries the ruleset files inside its own archive, so `rulesets/codesize.xml` resolves even though nothing named that exists on your filesystem.

## What a ruleset file actually contains

The YAML form is the one the README shows, and it is short. It names and describes the ruleset, lists the paths to scan, lists patterns to exclude, and then references the rule set files you want:

```yaml
name: My first PHPMD rule set
description: My custom rule set that checks my code...
path:
  - "src/"
exclude-pattern:
  - "*/vendor/*"
rules:
  - ref: rulesets/codesize.xml
```

Those six built-in rulesets are the vocabulary. `codesize` covers the size measures such as excessive method length and too many methods on a class. `cleancode` reports structural smells. `controversial` is the one to be careful with, because it encodes preferences that reasonable teams disagree about. `design` reports coupling and dependency problems. `naming` checks identifier conventions. `unusedcode` finds code nothing references.

The same structure can be written in XML, JSON or PHP, and the README links a separate document on creating a custom ruleset for the details of each format. That flexibility is the reason to pick YAML over the XML defaults if you are starting fresh: it is the format a person can review in a pull request without a parser.

Filtering happens in two directions. `--minimum-priority` and `--maximum-priority` are threshold options, so on a large codebase you can run only the highest-priority rules first and widen the range as the count comes down. `--exclude` takes comma-separated patterns with asterisks, and the same thing can be expressed as `exclude-pattern` inside the ruleset, which is the better place for it since it travels with the configuration.

## Baselines and suppression attributes, the actual adoption mechanism

This is the part of PHPMD that decides whether you can use it on code you did not write. Running a design smell detector over a mature codebase produces hundreds of findings, and a team that cannot act on all of them will disable the tool entirely.

The baseline workflow exists for exactly that. `--generate-baseline` writes a `phpmd.baseline.xml` next to the ruleset file, recording the violations that already exist, with paths relative to the current working directory. From then on, only new findings are reported. `--update-baseline` is the cleanup counterpart: it removes entries for violations that no longer exist, so the file does not become an append-only archive. `--baseline-file` points at a custom location if the default `phpmd.baseline.xml` will not do. The rule is that violations absent from the baseline are reported as usual and are not silently added to it, which stops the baseline from drifting upward every time someone runs it.

The second mechanism is suppression at the source. A node annotated with `#[SuppressWarnings]` is not reported, and `--strict` switches that off so those nodes get reported too, with `--not-strict` as the default. So a team can migrate gradually either way: silence known problems in place, or hold the line globally with a baseline and let individual suppressions accumulate.

The remaining piece for CI is exit behaviour. By default a finding is a non-zero exit, which is what you want in a pipeline, but `--ignore-violations-on-exit` and `--ignore-errors-on-exit` each let the command exit zero, which is what you want when the report is going somewhere a human reads it.

## Caching, report formats and CI ergonomics

PHPMD does real work per run, so it has a cache. `--cache` enables it and defaults to `.phpmd.result-cache.php` in the working directory, with `--cache-file` to move it. The subtle option is `--cache-strategy`, which chooses between basing freshness on file contents and basing it on the modified timestamp. Content is slower and correct across the sorts of tooling that preserve timestamps; timestamp is fast and occasionally wrong after a checkout or a branch switch.

Output is configurable in both format and destination. The default format is `text`, changed with `--format`, and `--reportfile-text`, `--reportfile-xml` and `--reportfile-html` write reports to files, with several formats written at once. The text renderer does more than print lines: with verbosity raised it links each error to the documentation for that rule and formats the location in a way most IDEs convert into a clickable link. `--verbose`, `-v`, `-vv` and `-vvv` raise that level, and extra output goes to `STDERR` so it does not corrupt the report.

The niceties are the ones that show the tool has been run in a real pipeline. `--color` colours the text renderer. `--no-progress` drops the progress bar for log-friendly output. `--extra-line-in-excerpt` sets how much context an HTML report shows around a finding. `--coverage` takes a Clover-style report as produced by PHPUnit's own `--coverage-clover`, so coverage data can inform which files are worth analysing at all. `--suffixes` limits which extensions count as source. `--input-file` reads a list of paths from a file, `--bootstrap` runs a script before analysis, and `--jetbrains=PROJECT_NAME` produces `jetbrains://` links that open files in a named project. Release 2.14.0 added file globbing and reading paths from STDIN to that list.

## A 3.x default branch sitting next to 2.x releases

Here is a discrepancy worth understanding before you pin a version. GitHub reports the default branch as `3.x`, and the last push was on 2026-09-26, which is recent work on a branch named for a major version. The newest published release, however, is 2.15.0 from 2023-12-11. The README's own badges still point at `master`, which matches neither.

Both facts are in the repository at once, and the README does not explain the gap. The practical reading is that 3.x is in development and has not been released as a version you can pin, while the 2.x line is what Composer installs today and what the release notes describe. If you want the 3.x code you have to check out the branch, and if you want a released tool you get 2.15.0.

What 2.x did add is worth knowing, because it tells you the project's recent direction. Release 2.14.0 in September 2023 brought phar signing, result caching, PHPStan in CI, verbosity and colour for the text renderer, and file globbing with STDIN support. Release 2.15.0 in December 2023 allowed options and values separated by an equals sign and required pdepend 2.16.1, which is where Symfony 7 compatibility and PHP 8.3 syntax support arrived. In other words, the dependency that does the actual parsing has been keeping pace with PHP releases, and 2.15.0 is the release that would let the tool read PHP 8.3 code.

The tree explains how the project is put together and how carefully it is maintained: `src/`, `tests/`, `rulesets/`, `conf/`, `bin/` and `scripts/` for the code, alongside `phpunit.xml.dist`, `phpstan.neon`, `phpcs.xml.dist` and `.php-cs-fixer.php` for its own quality gates, `UPGRADING.md`, `AUTHORS.rst` and a `CHANGELOG`. There is a `.gitmodules`, so some content is pulled from other repositories. The topics include hacktoberfest. The gap to ask about is whether 3.x will be released and when; nothing in the repository answers it.

## Conclusion

PHPMD earns its place on a legacy PHP codebase where nobody is going to fix two thousand violations this week, because the baseline workflow lets you record the existing debt and hold the line from there. It is not a type checker and it does not pretend to be: reach for PHPStan or Psalm when the question is whether a call site is wrong, and reach for this when the question is whether a class has too many reasons to change. Note the version split before you pin anything: the default branch is `3.x` while the newest published release is 2.15.0 from 2023-12-11, so a Composer install and a checkout of the default branch are not the same code. Start by running `phpmd init`, generating a baseline, and checking that the remaining report is short enough that someone will actually read it.

## FAQ

### How do I install and run PHPMD from the command line?

Install it through Composer or grab the Phar, then run `phpmd analyze src/` with the paths you want checked. If you do not pass `--ruleset`, PHPMD looks for `phpmd.yml`, `phpmd.yaml`, `phpmd.json`, `phpmd.xml` or `phpmd.php` in the current directory, including dot-prefixed and `.dist` variants. You can also run `phpmd init` to generate a configuration file through a wizard.

### What are PHPMD's built-in rulesets?

Six are referenced by name in the README's example: `codesize`, `cleancode`, `controversial`, `design`, `naming` and `unusedcode`. You reference them as `ref: rulesets/codesize.xml` in a ruleset file, or pass one directly with a command like `phpmd analyze --ruleset codesize src/`.

### How do I stop PHPMD reporting problems in code I cannot fix yet?

Generate a baseline with `--generate-baseline`, which writes `phpmd.baseline.xml` next to the ruleset, so only new violations get reported. `--update-baseline` prunes entries that no longer apply, and `--baseline-file` points to a different location. Individual nodes can also carry a `#[SuppressWarnings]` attribute, which `--strict` overrides.

### Is PHPMD still being developed?

Commits are recent, with the last push reported on 2026-09-26 on a default branch named `3.x`, but the newest published release is 2.15.0 from 2023-12-11. That release required pdepend 2.16.1, which brought Symfony 7 compatibility and PHP 8.3 syntax support to the underlying parser.

## Sources

- [License: BSD-3-Clause](https://github.com/phpmd/phpmd/blob/3.x/LICENSE)
- [phpmd/phpmd on GitHub](https://github.com/phpmd/phpmd)
- [Project website](https://phpmd.org)
- [README](https://github.com/phpmd/phpmd/blob/3.x/README.md)
- [Releases](https://github.com/phpmd/phpmd/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/phpmd-phpmd
