Drupal Coding Standards at CI Parity: phpcs & phpstan
When to Use
Use this when setting up local code-quality enforcement to match what the CI pipeline checks. The goal is zero surprises when phpcs or phpstan runs on your MR.
Decision
| Ruleset | Role | CI behavior |
|---|---|---|
Drupal |
Mandatory — formatting, naming, control structures | Blocking (allow_failure: false by default) |
DrupalPractice |
Recommended — best practices, common mistakes; may false-positive | Same job; phpcs is blocking |
JS/CSS:
drupal/coderdropped JS/CSS sniffs. Use ESLint and Stylelint for those.
Pattern
Install and run locally:
# Install as dev dependency (preferred for team consistency)
composer require --dev drupal/coder squizlabs/php_codesniffer
# Register the Drupal standards
./vendor/bin/phpcs --config-set installed_paths vendor/drupal/coder/coder_sniffer
./vendor/bin/phpcs -i # confirms "Drupal" and "DrupalPractice" are listed
# Check your module
./vendor/bin/phpcs --standard=Drupal,DrupalPractice \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
path/to/module
# Fix loop: auto-fix → re-run → commit clean code
./vendor/bin/phpcbf --standard=Drupal,DrupalPractice path/to/module
phpcs.xml.dist at the project root:
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="my_module">
<description>Drupal coding standards for my_module.</description>
<file>src</file>
<file>tests</file>
<rule ref="Drupal"/>
<rule ref="DrupalPractice"/>
<arg name="extensions" value="php,module,inc,install,test,profile,theme,yml"/>
</ruleset>
PHPStan setup (phpstan.neon):
includes:
- vendor/mglaman/phpstan-drupal/extension.neon
parameters:
level: 2
paths:
- src
- tests
Suppress a DrupalPractice false positive (narrow scope):
// phpcs:ignore DrupalPractice.General.OptionsT9n.MissingT9n
$options = ['value' => 'Value'];
Key Standards Rules
- Naming: functions
module_function_name(); constantsUPPER_CASE; classesUpperCamelCase; methodslowerCamelCase; interfaces suffixInterface; traits suffixTrait; test classes suffixTest - Formatting: 2-space indent (never tabs); ~80-char line target;
['key' => 'value']short array syntax; single quotes by default - Type hints: mandatory in Drupal 9+ code; return types required
- Docblocks: every function/class/method/property/constant needs one; hook implementations documented as
Implements hook_name().
Common Mistakes
- Wrong: Running
phpcbfand assuming it fixed everything → Right: Re-runphpcsafter auto-fix; some violations require manual resolution - Wrong: Routing JS/CSS questions to
drupal/coder→ Right: It no longer handles those; use ESLint/Stylelint - Wrong: Hardcoding PHPUnit/PHPStan version constraints in
composer.json→ Right: Resolve from the target core'sdrupal/core-dev - Wrong: Setting
level: 9PHPStan on a new module with no baseline → Right: Start at 2–4 and raise it - Wrong: Using a global
phpcsinstall → Right: Per-project--devdependency avoids version skew