Summary
The phpstan.neon the add-on installs (from the Drupal GitLab CI template) limits analysis to:
paths:
- web/modules/custom
- web/themes/custom
- web/sites
Projects that keep their custom modules somewhere other than web/modules/custom get zero PHPStan coverage of that code, silently. There's no error — PHPStan runs green because it simply never looks at the files.
Real-world example
On a project using a web/modules/common/ directory for custom modules, web/modules/custom/ contained only a .gitkeep. PHPStan reported success on every run while never analysing a single line of the project's actual custom code. The gap was only noticed by accident when comparing IDE diagnostics (intelephense/phpcs) against PHPStan output on the same file.
This is easy to miss because:
web/modules/custom is the template default, so it's rarely revisited.
- An empty or near-empty custom dir still makes PHPStan exit 0.
ddev checks shows PHPStan passing, which reads as "analysed and clean," not "analysed nothing."
Suggested change
Default to analysing all of web/modules and carve out contrib, so custom code is covered regardless of a project's directory convention:
paths:
- web/modules
- web/themes/custom
- web/sites
excludePaths:
analyseAndScan:
- web/modules/contrib/*
# ...existing web/sites excludes...
Notes on the specifics:
analyseAndScan (not analyse) for contrib: mglaman/phpstan-drupal resolves contrib classes on demand via Drupal's autoloader, so PHPStan still understands calls into contrib without scanning the whole contrib tree (faster, and avoids parse errors in third-party code).
- The
/* is deliberate: PHPStan matches excludePaths with fnmatch where * crosses /, so web/modules/contrib/* excludes the subtree recursively.
Adoption caveat worth documenting
Broadening coverage will surface a batch of previously-unseen findings on existing projects. The add-on could pair this with guidance to run ddev phpstan --generate-baseline on first adoption so old debt is captured and only new regressions fail.
Possible approaches for the add-on
- Change the installed default to the
web/modules + contrib-exclusion form above.
- Or detect custom-code locations at install time (scan for
web/modules/*/ dirs containing .info.yml outside contrib) and write matching paths.
- At minimum, document the limitation and the one-line fix so users know to check whether their custom code is actually in scope.
Summary
The
phpstan.neonthe add-on installs (from the Drupal GitLab CI template) limits analysis to:Projects that keep their custom modules somewhere other than
web/modules/customget zero PHPStan coverage of that code, silently. There's no error — PHPStan runs green because it simply never looks at the files.Real-world example
On a project using a
web/modules/common/directory for custom modules,web/modules/custom/contained only a.gitkeep. PHPStan reported success on every run while never analysing a single line of the project's actual custom code. The gap was only noticed by accident when comparing IDE diagnostics (intelephense/phpcs) against PHPStan output on the same file.This is easy to miss because:
web/modules/customis the template default, so it's rarely revisited.ddev checksshows PHPStan passing, which reads as "analysed and clean," not "analysed nothing."Suggested change
Default to analysing all of
web/modulesand carve out contrib, so custom code is covered regardless of a project's directory convention:Notes on the specifics:
analyseAndScan(notanalyse) for contrib:mglaman/phpstan-drupalresolves contrib classes on demand via Drupal's autoloader, so PHPStan still understands calls into contrib without scanning the whole contrib tree (faster, and avoids parse errors in third-party code)./*is deliberate: PHPStan matchesexcludePathswith fnmatch where*crosses/, soweb/modules/contrib/*excludes the subtree recursively.Adoption caveat worth documenting
Broadening coverage will surface a batch of previously-unseen findings on existing projects. The add-on could pair this with guidance to run
ddev phpstan --generate-baselineon first adoption so old debt is captured and only new regressions fail.Possible approaches for the add-on
web/modules+ contrib-exclusion form above.web/modules/*/dirs containing.info.ymloutsidecontrib) and write matchingpaths.