micheh/phpcs-gitlab
A PHP_CodeSniffer report for GitLab CI that outputs CodeClimate-compatible JSON, so coding-standard violations appear directly in merge request code quality reports and pipeline widgets.
micheh/phpcs-gitlab is a custom PHP_CodeSniffer report that emits the JSON array GitLab expects for its Code Quality artifact. It is not a linter or a standard — it only formats PHPCS results.
Minimal setup in a Laravel app:
composer require --dev micheh/phpcs-gitlab
# if you do not already have PHPCS:
composer require --dev squizlabs/php_codesniffer
Run it once locally against a committed phpcs.xml / phpcs.xml.dist:
vendor/bin/phpcs \
--standard=phpcs.xml \
--report='\Micheh\PhpCodeSniffer\Report\GitLab' \
--report-file=gl-code-quality-report.json
The leading backslash and fully-qualified class name are required so PHPCS treats the value as a report class rather than a built-in report alias. Use single quotes in the shell; double the backslashes in JSON/composer scripts.
Inspect the result:
jq 'type, length, .[0]' gl-code-quality-report.json
# should be: "array", a count, and an object with description, check_name,
# fingerprint, severity, location.path, location.lines.begin
Wire it into .gitlab-ci.yml:
code_quality:
stage: test
image: php:8.3-cli
before_script:
- composer install --no-interaction --prefer-dist
script:
- |
set +e
vendor/bin/phpcs --standard=phpcs.xml \
--report='\Micheh\PhpCodeSniffer\Report\GitLab' \
--report-file=gl-code-quality-report.json
# always upload a valid artifact, even if PHPCS bailed early
[ -f gl-code-quality-report.json ] || echo '[]' > gl-code-quality-report.json
exit 0
artifacts:
when: always
expire_in: 1 week
reports:
codequality: gl-code-quality-report.json
allow_failure: true
First use case: you want PHPCS violations to appear in the MR Code Quality widget and diff annotations instead of only failing a job with terminal output.
Where to look first: the package README for the exact report class name and PHPCS version matrix; GitLab's "Code Quality report" docs for the artifact schema (description, fingerprint, severity, location). For Laravel, start by defining a phpcs.xml that scans app/, routes/, database/, config/, tests/ and excludes vendor/, storage/, bootstrap/cache/, public/build/.
Note: Laravel Pint is built on PHP-CS-Fixer, not PHP_CodeSniffer. If your team only runs Pint, this package cannot consume Pint output. Either add PHPCS alongside Pint (e.g. PSR-12 or Slevomat) or use a different GitLab code-quality producer.
Keep the GitLab report separate from your blocking lint. Run the normal vendor/bin/phpcs (human-readable, blocking) and the GitLab report in a dedicated non-blocking job, or run PHPCS twice. PHPCS writes one report per invocation to --report-file, so a single command will not give you both a readable console summary and the artifact cleanly.
Register the report in a dedicated ruleset instead of typing the class every time:
<!-- phpcs-gitlab.xml -->
<ruleset name="GitLab CI">
<file>app</file>
<file>routes</file>
<file>database</file>
<file>tests</file>
<exclude-pattern>vendor/*</exclude-pattern>
<exclude-pattern>storage/*</exclude-pattern>
<exclude-pattern>bootstrap/cache/*</exclude-pattern>
<arg name="report" value="\Micheh\PhpCodeSniffer\Report\GitLab"/>
<arg name="report-file" value="gl-code-quality-report.json"/>
</ruleset>
Then vendor/bin/phpcs --standard=phpcs-gitlab.xml. Keep this out of your default phpcs.xml so local runs do not constantly rewrite a JSON file.
Composer script (note the escaped backslashes):
"scripts": {
"lint": "phpcs",
"lint:gitlab": "phpcs --standard=phpcs-gitlab.xml --report=\\Micheh\\PhpCodeSniffer\\Report\\GitLab --report-file=gl-code-quality-report.json"
}
MR-only pipelines. Make the job run on merge request pipelines and on the default branch so GitLab can compare source and target reports:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Incremental adoption on a legacy Laravel codebase. Scan only changed PHP files to keep the report small and actionable:
FILES=$(git diff --name-only "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME...HEAD" -- '*.php' | tr '\n' ' ')
if [ -n "$FILES" ]; then
vendor/bin/phpcs $FILES --standard=phpcs-gitlab.xml
else
echo '[]' > gl-code-quality-report.json
fi
Two-tier quality gate. Keep composer lint blocking on new code, and let the code-quality artifact be advisory (allow_failure: true). This gives reviewers in MRs visibility without freezing the pipeline on pre-existing debt.
Local debugging loop. Run the exact CI command with the same working directory as the repo root, then open the JSON with jq. If the MR widget shows nothing, compare the artifact from your branch pipeline with the one from the target branch — the widget is diff-based.
vendor/bin/phpcs. The report class is loaded through Composer's autoloader. A globally installed PHPCS will not see it unless you pass --bootstrap=vendor/autoload.php.--report-file is mandatory. Without it the JSON goes to stdout and is mixed with progress/summary text, producing invalid JSON that GitLab silently rejects.set -e or a default CI shell, the job aborts before artifacts upload. Use allow_failure, || true, set +e, or --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 — but verify the report file was still written.[ -f gl-code-quality-report.json ] || echo '[]' > gl-code-quality-report.json and use artifacts: when: always.location.path must be relative to the repository root. Run from $CI_PROJECT_DIR (set WORKDIR correctly in Docker), and use --basepath=. if absolute paths leak into the report.--cache in CI. Cached files can be omitted from reports, producing an incomplete artifact. If you must cache, use a per-commit cache file and clear it between pipelines.vendor/, storage/, bootstrap/cache/, public/build/, ide helpers, and test fixtures otherwise flood the MR widget.info/minor/major/critical/blocker). Use PHPCS's own severity attribute in the ruleset to suppress low-value warnings before they reach GitLab.description, fingerprint, severity, location.path, and location.lines.begin. jq 'type' gl-code-quality-report.json should print "array".Micheh\PhpCodeSniffer\Report\GitLab, override the relevant methods, and point --report at your class (keep it autoloadable via Composer).squizlabs/php_codesniffer) rather than phpcsstandards/php_codesniffer.--report=\Micheh\PhpCodeSniffer\Report\GitLab in composer.json must be written as --report=\\Micheh\\PhpCodeSniffer\\Report\\GitLab, or PHPCS will look for a report named Micheh....How can I help you explore Laravel packages today?