irstea/phpmd-shim
A lightweight Composer shim for PHP Mess Detector (PHPMD) that lets PHP projects run PHPMD static analysis without a globally installed binary. It simplifies setup for local development and CI, helping teams enforce code-quality rules consistently.
irstea/phpmd-shim is not a replacement for PHP Mess Detector — it is a thin packaging layer intended to make phpmd/phpmd installable in environments where the upstream distribution path is unreliable (restricted network, blocked Packagist mirrors, upstream metadata conflicts, or version pinning inside the IRSTEA GitLab perimeter). Treat it as infrastructure, not as a code analysis tool.
Read the composer.json first. Everything the shim does is described by three fields:
type — "composer" means it is a package repository that serves phpmd artifacts; "vcs"/"git" means it is a wrapper package you require by name.replace / provide — if present, it satisfies a phpmd/phpmd constraint without pulling the real package.bin — if present, it ships a vendor/bin/phpmd wrapper (often a PHAR launcher).Wire it in as a repository, not a dependency, in the common case:
{
"repositories": [
{ "type": "composer", "url": "https://gitlab.irstea.fr/pole-is/tools/phpmd-shim.git" }
],
"require-dev": {
"phpmd/phpmd": "^2.15"
}
}
Then composer update phpmd/phpmd and confirm vendor/bin/phpmd --version resolves to the shim-supplied build. That is your first-use test: if vendor/bin/phpmd src text cleancode runs cleanly, the shim is doing its job (nothing visible) and you can continue using phpmd's normal CLI and rule configuration.
Pin phpmd through the shim, not around it. Do not lock phpmd/phpmd to a commit hash in the root composer.json and also install the shim — you end up with two sources for the same package. Instead, keep the constraint loose (^2.15) and let the shim's own version metadata pick the concrete build. The shim exists precisely so this resolution stays stable across machines and CI.
Use it to unblock CI. In a pipeline that cannot reach Packagist, the standard pattern is:
- composer config repositories.phpmd-shim composer https://gitlab.irstea.fr/pole-is/tools/phpmd-shim.git
- composer install --no-interaction --prefer-dist
- vendor/bin/phpmd app text phpmd.xml
Prefer --prefer-dist so the shim downloads tarballs rather than cloning; shims are usually metadata-only and cloning adds nothing.
Combine with a committed phpmd.xml. The shim handles delivery of the binary; your project owns the ruleset. Keep phpmd.xml (or phpmd.xml.dist) in the repo root and run phpmd against it explicitly. Do not rely on phpmd's default ruleset — it changes between upstream versions, and the shim's update cadence is not guaranteed to track upstream.
Wrap it in Composer scripts so developers never invoke vendor/bin/phpmd by hand:
"scripts": {
"lint:phpmd": "phpmd app,src text phpmd.xml --exclude vendor,tests"
}
This keeps the invocation identical whether the shim is installed locally or in CI.
Integrate into pre-commit and IDE tooling via the resolved binary path. PhpStorm's "PHP Mess Detector" inspection wants an absolute path; point it at vendor/bin/phpmd (which after install is the shim's output) and it will work unchanged. Similarly, hook composer lint:phpmd into a Husky/pre-commit step rather than referencing phpmd directly.
A shim is not a fork. It is almost certainly tracking upstream phpmd/phpmd releases with little or no delay. Do not expect bug fixes for phpmd itself to land here; report those upstream and wait for the shim to re-publish.
Do not report phpmd bugs to this repository. The GitLab issue tracker at pole-is/tools/phpmd-shim is for packaging problems (install failures, version mismatch, dead artifact URLs) — not rule false positives or parser errors.
Zero dependents and no stars is a signal. With Dependents: 0 and a score of 0, assume the shim is maintained for internal IRSTEA use and may lag or break without notice. Pin your phpmd constraint tightly in composer.lock, and run composer update phpmd/phpmd deliberately, in a branch, before rolling forward.
Authentication is the most common install failure. The GitLab host is likely behind SSO or a VPN. If composer install fails with a 404 on the repository URL, it is almost always auth (missing COMPOSER_AUTH token / no SSH key / not on the VPN), not a missing package. Test with:
composer show -a phpmd/phpmd --repository=https://gitlab.irstea.fr/pole-is/tools/phpmd-shim.git
A 401/403 confirms the cause; a 200 with an empty version list means the shim is reachable but the version you asked for is not published there yet.
Clear the Composer cache after switching sources. If you previously installed phpmd/phpmd from Packagist and now route through the shim, stale metadata resolves to the old package and the shim silently does nothing. Run composer clearcache && rm -rf vendor composer.lock && composer install when first adopting it.
Check what vendor/bin/phpmd actually is. In the binary-wrapper case it is a shell/PHP script that invokes a vendored PHAR. If you commit vendor/ or ship it in a Docker image, confirm the wrapper's shebang and the PHAR path survive your build (some multi-stage builds strip bin/ symlinks — use cp -a or composer install inside the final stage).
Rule-set caching. phpmd itself caches nothing, but Composer's autoloader does. If you add a custom rule class, run composer dump-autoload — a shim-installed autoloader is loaded from vendor/autoload.php exactly as upstream, so this is not shim-specific, but developers new to the shim often assume otherwise.
Extension point: your own ruleset, not the shim. Extend phpmd the way you always would — add a <rule ref="..."> in phpmd.xml or register a custom rule class in composer.json under autoload. The shim's only extension surface is the Composer repository entry; do not attempt to override its internals.
Version drift across projects. Different projects may resolve to different phpmd versions through the same shim if their constraints differ. Standardise by sharing a phpmd.xml / ruleset and a common ^2.x constraint across your repositories, and add a single "quality" job to your shared CI template so all pipelines exercise the same phpmd build.
How can I help you explore Laravel packages today?