DDEV add-on providing code quality validation commands for Drupal module development. The commands and their default configurations match the Drupal GitLab CI template from the Drupal Association, so code that passes locally will pass in CI.
All tools are installed at container build time — they are available immediately after ddev restart with no per-start installation overhead.
- If you haven't already, install Docker and DDEV.
- Set up a Drupal project with DDEV, either by following the Drupal CMS quickstart or the Drupal core quickstart.
- Add this add-on and restart:
ddev add-on get mandclu/ddev-module-developer
ddev restart- Optional — for
eslintandstylelintto use Drupal core's own configuration files (the same ones used in CI), install core's JavaScript dependencies:
ddev exec "cd web/core && yarn install"Without this step both commands still work using the bundled fallback configurations.
This add-on provides the following DDEV commands, all running inside the web container.
ddev phpcs— Run PHP_CodeSniffer against Drupal coding standards.ddev phpcbf— Auto-fix phpcs violations using PHP Code Beautifier and Fixer.ddev phpstan— Run PHPStan static analysis with the Drupal extension.ddev phpmd— Run PHP Mess Detector.ddev rector— Run Drupal Rector to find and fix deprecated API usage.ddev stylelint— Run Stylelint on CSS/SCSS files.ddev eslint— Run ESLint on JavaScript and YAML files, with Prettier formatting checks.ddev cspell— Run CSpell spell-checking across project files.
Pass a path as the first argument to any command to target a specific file or directory:
ddev phpcs web/modules/custom/mymodule
ddev phpstan analyse web/modules/custom/mymodule
ddev stylelint 'web/modules/custom/mymodule/**/*.css'
ddev eslint web/modules/custom/mymodule/js
ddev phpmd web/modules/custom/mymodule textRun without a path argument to use each tool's own default discovery (where supported).
Each command follows a three-level priority for its configuration:
- Project config — if a config file exists in your project root (e.g.
phpcs.xml,phpstan.neon,.stylelintrc.json), it is used as-is. - Drupal core config — for
eslintandstylelint, ifcore/node_modulesis installed, the command usescore/.eslintrc.passing.jsonandcore/.stylelintrc.jsonrespectively — the same files used in the GitLab CI pipeline. - Bundled defaults — if neither of the above is found, the add-on's own default configs in
.ddev/module-developer/config/are used. These match the Drupal GitLab Templates defaults.
Place a config file in your project root to override the bundled default for any tool:
| Tool | Config file(s) |
|---|---|
| phpcs / phpcbf | phpcs.xml or phpcs.xml.dist |
| phpstan | phpstan.neon or phpstan.neon.dist |
| rector | rector.php or rector.php.dist |
| stylelint | .stylelintrc.json (or any supported format) |
| eslint | .eslintrc.json (or any supported format) |
| cspell | .cspell.json |
The bundled phpcs.xml matches the GitLab Templates default: only the Drupal standard is enforced; DrupalPractice is commented out and can be enabled if required.
The bundled phpstan.neon runs at level 0, matching the GitLab Templates default. To raise the level, add a phpstan.neon to your project root:
parameters:
level: 2The Drupal GitLab CI Docker image pre-installs mglaman/phpstan-drupal, so the template's phpstan.neon does not reference it. In DDEV there is no such pre-built image, so when no project-level config is found the phpstan command automatically injects the extension. If you supply your own phpstan.neon and want the Drupal extension, add the includes explicitly:
includes:
- /usr/local/composer/vendor/mglaman/phpstan-drupal/extension.neon
- /usr/local/composer/vendor/mglaman/phpstan-drupal/rules.neonMost tools support an ignore file in the project root:
- phpcs:
<exclude-pattern>entries in yourphpcs.xml - stylelint:
.stylelintignore - eslint:
.eslintignore - cspell:
ignorePathsin.cspell.json
Set up a pre-commit hook that runs phpcbf before every commit:
- Create
.git/hooks/pre-commitif it does not already exist. - Add the following:
#!/usr/bin/env bash
ddev phpcbf -q- Make it executable:
chmod +x .git/hooks/pre-commit.
"Error: unknown command"
Commands are only available when the DDEV project type is drupal. Confirm the type in .ddev/config.yaml:
type: drupalTip
Run ddev restart after editing .ddev/config.yaml.
ESLint or Stylelint reports missing plugin errors
The bundled fallback configs use globally installed plugins. If you are pointing ESLint or Stylelint at a custom config that references plugins not installed globally, either install those plugins inside the container or add them to your project's package.json and run yarn install / npm install.
PHPStan cannot find Drupal classes
Make sure drupal_root is set correctly. When using the bundled config the phpstan command sets this automatically from $DDEV_DOCROOT. If you supply your own phpstan.neon, add:
parameters:
drupal:
drupal_root: /var/www/html/webAdjust the path to match your project's docroot.
Tests are written in Bats. Install the test helper submodules first:
git submodule update --initThen run the tests from the project root:
./tests/bats/bin/bats ./testsTests are triggered automatically on every push and run nightly. Contributions and bug reports are welcome.
Contributed and maintained by Martin Anderson-Clutz (@mandclu).