It turns out that the best way to integrate PHP CodeSniffer in GitLab CI (at least when using pipelinecomponents/php-codesniffer) is to set configuration in a phpcs.xml. This is important for projects like Drupal that need to source specific rulesets.

Example phpcs.xml for a Drupal project:

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="drupal">
  <description>PHP CodeSniffer configuration.</description>
  <file>./web/modules/custom</file>
  <arg name="extensions" value="php,module,inc,install,theme"/>
  <config name="drupal_core_version" value="9"/>
  <rule ref="vendor/drupal/coder/coder_sniffer/Drupal/ruleset.xml">
    <exclude name="Drupal.Commenting.Deprecated.DeprecatedWrongSeeUrlFormat"/>
  </rule>
</ruleset>

That reduces the script section to just the command:

phpcs:
  stage: test
  image: pipelinecomponents/php-codesniffer:0.29.0
  script:
    - phpcs

It is also helpful to have the report in junit format by redirecting output to an xml file and declaring that file as an artifact:

phpcs:
  stage: test
  image: pipelinecomponents/php-codesniffer:0.29.0
  script:
    - phpcs --report=junit > phpcs-report.xml
  needs:
    - job: build
      artifacts: true
  artifacts:
    reports:
      junit:
        - phpcs-report.xml

Here is a more complete example .gitlab-ci.yaml file of just “build” and “test” stages:

stages:
  - build
  - test

build:
  stage: build
  image: drupal:9.5-php8.1
  script:
    - apt-get update && apt-get install -y git unzip
    - composer install
  cache:
    untracked: true
    key:
      files:
        - composer.lock
    paths:
      - vendor
      - web/core
      - web/libraries
      - web/modules/contrib
      - web/themes/contrib
      - drush/commands
  artifacts:
    untracked: true
    paths:
      - ./*

phpcs:
  stage: test
  image: pipelinecomponents/php-codesniffer:0.29.0
  script:
    - phpcs --report=junit > phpcs-report.xml
  needs:
    - job: build
      artifacts: true
  artifacts:
    reports:
      junit:
        - phpcs-report.xml
  allow_failure: true

Some things to remember:

  • Pay attention to the cache section during debugging as the paths would come from previous runs as long as composer.lock does not change
  • Since the Drupal standards come from the coder module, the test build contains dev modules. Those dev packages would need to be removed in subsequent stages.