Using Coder/PHPCS in DrupalCI

Last updated on
13 January 2022

This documentation needs work. See "Help improve this page" in the sidebar.

This documentation covers adding a phpcs.xml.dist file to your drupal.org contrib project.

What the test runner (AKA testbot) does

The test runner tries to run PHPCS with Coder on all projects.

If you have a patch that changes files, it will run PHPCS on only those files.

If your patch changes the phpcs.xml.dist or phpcs.xml file, it will run PHPCS against the whole project.

If your project does not have a phpcs config file, the test runner will use Composer to require the newest version of Coder, and then run PHPCS against changed files with --standard=Drupal.

And finally: If your project has a Composer-based requirement of a specific version of Coder, the test runner will install that, and then run PHPCS against either the patched files or the whole project.

Configuring the test runner

If you want to override the default options of the test runner, this can be done by editing the build.assessment.validate_codebase.phpcs section in drupalci.yml:

build:
  assessment:
    validate_codebase:
      phpcs:
        # Whether to sniff all files, or only the files changed in the patch.
        # Defaults to false.
        sniff-all-files: true
        # Whether to terminate the test when coding standards violations are
        # detected. Note that this will not cause the test result to be marked
        # as failed, but it will terminate at the "Build successful" stage.
        # Remaining tests will not run.
        # Defaults to false.
        halt-on-fail: true
        # The version of Coder to use. If omitted, this will default to the
        # version of Coder specified in composer.json, or to the latest version.
        coder-version: '^8.2@stable'

How to specify Drupal coding standards

In your project's phpcs.xml(.dist) file, you should specify the sniffs you want as sniffs. Like this fragment from the Examples for Developers project:

<ruleset name="drupal_examples">
  <description>Default PHP CodeSniffer configuration for Examples for Developers.</description>
  <file>.</file>
  <arg name="extensions" value="css,inc,install,module,php,profile,test,theme"/>
  <rule ref="Drupal.Array"/>
  <rule ref="Drupal.CSS"/>
  <rule ref="Drupal.Classes"/>
  <rule ref="Drupal.Commenting">
      <exclude name="Drupal.Commenting.DocComment.MissingShort"/>
      <!-- TagsNotGrouped has false positives for @code/@endcode. See
        https://www.drupal.org/node/2502837 -->
      <exclude name="Drupal.Commenting.DocComment.TagsNotGrouped"/>
      <!-- We have to document hooks in a non-standard way in order to be
        informative -->
      <exclude name="Drupal.Commenting.HookComment.HookParamDoc"/>
      <exclude name="Drupal.Commenting.HookComment.HookReturnDoc"/>
  </rule>

Rules are specified by sniff name. This is in contrast to Drupal core, which specifies rules by file location. Don't do this. Use the sniff name.

If you still want to be able to run PHPCS locally, you can either add the dealerdirect/phpcodesniffer-composer-installer package to the composer require-dev section, or add the following to your phpcs.xml:

<config name="installed_paths" value="../../drupal/coder/coder_sniffer/Drupal,../../drupal/coder/coder_sniffer/DrupalPractice" />

Adding third-party sniffs

DrupalCI supports the following rulesets out of the box: Zend, Squiz, PSR2, PSR12, PSR1, PEAR, MySource, DrupalPractice and Drupal. Additional sniffs can be added by including the ruleset in composer.json, and defining the rules in phpcs.xml. Here is an example that adds a number of rules from the slevomat/coding-standard ruleset:

First, add the ruleset to composer.json:

$ composer require --dev slevomat/coding-standard

Then, customize your phpcs.xml file with the rules you'd like to include. Also, make sure to set the installed_paths configuration to the path of the ruleset, relative to the location where PHPCS is installed (which is usually in the ./vendor/ folder).

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="my_drupal_module">
    <description>PHP CodeSniffer configuration for a Drupal module.</description>

    <arg name="extensions" value="php,inc,module,install,info,test,profile,theme,css,js"/>
    <arg name="report" value="full"/>
    <arg value="p"/>

    <config name="installed_paths" value="../../drupal/coder/coder_sniffer/Drupal,../../drupal/coder/coder_sniffer/DrupalPractice,../../slevomat/coding-standard" />

    <file>.</file>

    <!-- Exclude third party libraries. -->
    <exclude-pattern>./vendor</exclude-pattern>

    <!-- Exclude unsupported file types. -->
    <exclude-pattern>*.gif</exclude-pattern>
    <exclude-pattern>*.less</exclude-pattern>
    <exclude-pattern>*.png</exclude-pattern>

    <!-- Minified files don't have to comply with coding standards. -->
    <exclude-pattern>*.min.css</exclude-pattern>
    <exclude-pattern>*.min.js</exclude-pattern>

    <rule ref="Drupal" />
    <rule ref="DrupalPractice" />

    <!-- Require the strict types declaration in every PHP file. -->
    <rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
        <properties>
            <property name="newlinesCountBetweenOpenTagAndDeclare" value="2"/>
        </properties>
    </rule>

    <!-- Require nullable types to be declared as such. -->
    <rule ref="SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue">
    </rule>

    <!-- Forbid annotations which are not included in the Drupal coding standard but are often added by IDEs. -->
    <rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
        <properties>
            <property name="forbiddenAnnotations" type="array">
                <element value="@author"/>
                <element value="@created"/>
                <element value="@copyright"/>
                <element value="@license"/>
                <element value="@package"/>
                <element value="@version"/>
            </property>
        </properties>
    </rule>

    <!-- Forbid documentation auto-generated by IDEs which does not align with the Drupal documentation standards. -->
    <rule ref="SlevomatCodingStandard.Commenting.ForbiddenComments">
        <properties>
            <property name="forbiddenCommentPatterns" type="array">
                <element value="/@inheritDoc/"/>
                <element value="/^Class [a-zA-z]*\.$/"/>
                <element value="/^Interface [a-zA-z]*\.$/"/>
                <element value="/^[a-zA-z]* constructor\.$/"/>
            </property>
        </properties>
    </rule>

    <!-- Enforce correct formatting of return types hints. -->
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing"/>

    <!-- Use statements should be ordered alphabetically. -->
    <rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
        <properties>
            <property name="caseSensitive" value="true"/>
        </properties>
    </rule>
</ruleset>

Help improve this page

Page status: Needs work

You can: