lvandi/php-crap-checker

A CLI tool to fail CI when PHP CRAP score exceeds a configured threshold.

Maintainers

Package info

github.com/LucianoVandi/php-crap-checker

pkg:composer/lvandi/php-crap-checker

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-26 07:50 UTC

This package is auto-updated.

Last update: 2026-04-26 08:05:28 UTC


README

CI Latest Stable Version PHP Version codecov License: MIT

A CI quality gate for method-level change risk in PHP projects.

Why this tool exists

Complex code with low test coverage is risky to change. The CRAP score captures this pattern in a single number. crap-check turns that signal into a CI gate: if any method exceeds the threshold, the build fails.

This matters in any workflow where code can be generated, refactored, or expanded quickly — including AI-assisted development — because static analysis and basic tests can pass while change risk quietly accumulates at method level.

What is the CRAP score?

CRAP (Change Risk Anti-Patterns) is a metric that combines cyclomatic complexity and test coverage. A method scores higher when it is both complex and poorly tested. The formula is:

CRAP(m) = comp(m)² × (1 - cov(m)/100)³ + comp(m)

A low CRAP score means the method is either simple, well-tested, or both. A high score is a signal worth investigating — not necessarily a hard blocker, but a starting point for review.

What this package does

  • Reads a Crap4J XML report generated by PHPUnit
  • Compares each method's CRAP score against a configurable threshold
  • Prints the violations sorted by severity
  • Exits with a non-zero code so CI fails when the threshold is exceeded
  • Diagnoses environment and PHPUnit configuration (doctor command)

What this package does NOT do

  • Generate code coverage (that is PHPUnit's job, with PCOV or Xdebug)
  • Replace tools like Codecov for overall coverage tracking
  • Apply per-method or per-path ignores (planned for a future release)
  • Read Clover or other coverage formats

Installation

composer require --dev lvandi/php-crap-checker

Usage

Basic usage

# 1. Generate the Crap4J report with PHPUnit
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml

# 2. Check the threshold
vendor/bin/crap-check check build/crap4j.xml --threshold=30

The report argument defaults to build/crap4j.xml and --threshold defaults to 30.

Options

Option Default Description
report build/crap4j.xml Path to the Crap4J XML report
--threshold 30 Maximum allowed CRAP score (exclusive)
--max-violations (none) Maximum number of violations before CI fails
--max-age (none) Maximum report age; accepts minutes (60), or duration strings (30m, 2h)
--format text Output format (text, json)

Usage in GitHub Actions

- name: Run PHPUnit with Crap4J coverage
  run: php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml

- name: Check CRAP threshold
  run: vendor/bin/crap-check check build/crap4j.xml --threshold=30

Full example with PHP matrix:

jobs:
  ci:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php: ["8.3", "8.4"]
    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: pcov
          coverage: pcov

      - run: composer install --no-interaction --prefer-dist

      - run: php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml

      - run: vendor/bin/crap-check check build/crap4j.xml --threshold=30

Usage with Codecov

php-crap-checker and Codecov serve different purposes and work well together:

- name: Run PHPUnit with coverage
  run: |
    php -d pcov.enabled=1 vendor/bin/phpunit \
      --coverage-clover build/clover.xml \
      --coverage-crap4j build/crap4j.xml

- name: Check CRAP threshold
  run: vendor/bin/crap-check check build/crap4j.xml --threshold=30

- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v5
  with:
    files: build/clover.xml
    fail_ci_if_error: true

Codecov tracks overall and patch coverage. crap-check enforces a per-method CRAP threshold. They complement each other.

Exit codes

Code Meaning
0 OK — no violations
1 CRAP threshold exceeded
2 Invalid input (e.g. non-numeric threshold)
3 Report file not found or not readable
4 Invalid XML in report
5 Report valid but contains no methods

Output examples

No violations

CRAP threshold OK. Max allowed: 30
Analyzed methods: 128
Violations: 0

Violations found

CRAP threshold exceeded. Max allowed: 30

3 violations found:

1) App\Legacy\ReportGenerator::generate()
   File: src/Legacy/ReportGenerator.php:10
   CRAP: 72.00
   Complexity: 18
   Coverage: 0.00%

2) App\Service\OrderImporter::import()
   File: src/Service/OrderImporter.php:42
   CRAP: 46.23
   Complexity: 12
   Coverage: 41.60%

3) App\Service\ShippingCalculator::calculate()
   File: src/Service/ShippingCalculator.php:88
   CRAP: 35.10
   Complexity: 9
   Coverage: 22.00%

Report not found

Report not found: build/crap4j.xml

Generate it with:
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-crap4j build/crap4j.xml

A note on PCOV and Xdebug

PCOV and Xdebug are not required by this package. They are required by PHPUnit to generate the Crap4J report. Once the report exists, crap-check reads the XML and needs no coverage driver.

In CI, install PCOV (faster) or Xdebug only in the step that generates coverage:

- uses: shivammathur/setup-php@v2
  with:
    php-version: "8.3"
    extensions: pcov
    coverage: pcov

If you already have a report from a previous step or artifact, you can run crap-check without any coverage extension.

Interpreting a high CRAP score

A high CRAP score on a method is a signal, not a verdict. Before raising the threshold or suppressing the check, consider:

  • Add tests: if the method is complex but untested, coverage will bring the score down
  • Reduce complexity: extract helper methods or simplify branching logic
  • Accept and document: some methods are inherently complex and well-understood — a future baseline feature will allow explicit exceptions

Mechanically writing tests just to lower the number defeats the purpose. Use the score to guide meaningful improvement.

Adopting on a legacy project

If your codebase already has many violations, a zero-tolerance threshold from day one is counterproductive. See docs/ADOPTING.md for gradual adoption strategies: start permissive and tighten over time, cap violations with --max-violations, and how to talk to your team about the metric.

Planned

Future releases may add:

  • baseline support (snapshot a list of known violations, block only new ones)
  • --fail-on=new and --fail-on=worsened
  • ignore rules for paths and specific methods
  • GitHub Actions annotations

Requirements

  • PHP >=8.3
  • ext-simplexml
  • symfony/console ^7.0

Development

PHP runs inside Docker. Build the image once (uses your host UID/GID to avoid file permission issues):

make build
make install

Common commands:

make test        # PHPUnit
make qa          # PHPUnit + PHPStan (CI gate)
make stan        # PHPStan level 9
make cs-fix      # PHP CS Fixer
make infection   # Mutation testing

Contributing

Contributions are welcome. See CONTRIBUTING.md for guidelines on opening issues and submitting pull requests.

To report a security vulnerability, follow the process described in SECURITY.md.

This project is released under the MIT License.