drew/debug-statements-fixers
Laravel Pint fixers that automatically detect and remove leftover debug statements such as dd(), dump(), var_dump(), print_r(), and ray() from your PHP codebase. Easily keep commits and production code clean, consistent, and free of accidental debugging calls.
composer require --dev drew/debug-statements-fixers friendsofphp/php-cs-fixer
The package is a PHP-CS-Fixer plugin, not a standalone tool — it ships fixer classes (Dump, VarDump, PrintR, DebugPrintR, etc.) that you register into a PHP-CS-Fixer config. So the first thing to check is composer.json inside vendor/drew/debug-statements-fixers: it was released in 2018, so expect a friendsofphp/php-cs-fixer: ^2.x constraint. Confirm which major version Composer actually resolved before going further:
composer show friendsofphp/php-cs-fixer drew/debug-statements-fixers
ls vendor/drew/debug-statements-fixers/src
Where to look first: the src/ directory listing gives you the class names, and each class's getName() gives you the rule name to put in setRules(). You can also discover them from the CLI:
vendor/bin/php-cs-fixer describe Akovalyov/dump # exact name from getName()
vendor/bin/php-cs-fixer list-files --rules # or inspect the registered set
A minimal .php-cs-fixer.php (.php_cs on the 2.x line):
<?php
$finder = PhpCsFixer\Finder::create()
->in([__DIR__.'/app', __DIR__.'/routes', __DIR__.'/database', __DIR__.'/tests'])
->exclude(['storage', 'bootstrap/cache']);
return (new PhpCsFixer\Config())
->registerCustomFixers([
new Akovalyov\DebugStatementsFixers\Dump(),
new Akovalyov\DebugStatementsFixers\VarDump(),
new Akovalyov\DebugStatementsFixers\PrintR(),
])
->setRiskyAllowed(true)
->setRules([
'Akovalyov/dump' => true,
'Akovalyov/var_dump' => true,
'Akovalyov/print_r' => true,
// clean up the blank lines the removals leave behind
'no_extra_blank_lines' => true,
])
->setFinder($finder);
First use case — the one that pays for itself: a CI/pre-push guard.
vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no app/ routes/
Exit code 8 means "would have changed files" → fail the build with the diff in the log. This catches the dd($user) someone left in a controller at 23:40 before it reaches staging.
Dedicated guard config, separate from formatting. Keep two configs: .php-cs-fixer.php for full style (if you use it at all) and .php-cs-fixer.debug.php containing only the debug fixers. The guard config is fast, has near-zero diff noise, and won't reflow the codebase when you only wanted to catch dump().
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.debug.php --dry-run --diff
Local autofix vs. CI dry-run. Same config, different flags — developers run fix, CI runs fix --dry-run:
{
"scripts": {
"lint:debug": "php-cs-fixer fix --config=.php-cs-fixer.debug.php --dry-run --diff --using-cache=no",
"fix:debug": "php-cs-fixer fix --config=.php-cs-fixer.debug.php --diff"
}
}
Check only what changed. On a large repo, scanning everything is slow and surfaces legacy debt you can't fix today. In CI, feed the finder the diff:
FILES=$(git diff --name-only --diff-filter=ACMR origin/main... | grep -E '\.php$')
[ -n "$FILES" ] && vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.debug.php \
--path-mode=intersection --dry-run --diff $FILES
Coexist with Pint. Laravel Pint wraps PHP-CS-Fixer internally and does not expose registerCustomFixers, so this package cannot be plugged into pint.json. Run them as two independent steps (Pint for style, this for the debug guard) — don't try to merge them.
Layer with grep for what fixers can't see. PHP-CS-Fixer only parses .php files. Blade and config-free spots need a separate check:
git grep -nE '\b(dd|dump|ray|var_dump|print_r)\s*\(' -- '*.blade.php' '*.php' && exit 1 || true
This is also your safety net for Log::debug, logger()->debug() and any custom debug helpers the package doesn't know about.
Pre-commit hook. Wire fix:debug into CaptainHook / Husky / a plain .git/hooks/pre-commit so the statement never lands in the index at all — cheaper than a CI round trip.
Editor integration. Any PHP-CS-Fixer plugin for PhpStorm/VS Code can point at .php-cs-fixer.debug.php with "fix on save", giving you automatic removal during normal editing.
friendsofphp/php-cs-fixer: ^3.0; a 2018 package almost certainly requires ^2.x. Composer will hard-fail on composer require --dev. Options: install the tool in a separate tools/composer.json (or a global Composer bin dir), run it from a phar, or — the pragmatic answer — vendor the fixer classes. They're a handful of small classes; copying them into tools/fixers/ and adapting the FixerInterface signatures to v3 removes a dead dependency entirely. With 22 stars, 0 dependents and a last release in 2018, treat it as reference source, not a maintained dependency.dd($x) on its own line → fine. return dd($x);, $x = dd($x);, foo(dd($x)), $x = $y ?: dump($z); → you can get a syntax error or a silently changed return value. Always review --dry-run --diff output on unfamiliar code, and run the test suite after any autofix commit.dd(tap($user, fn ($u) => var_dump($u))); may only lose the outer (or inner) call. Read the diff; don't --dry-run-and-merge blind.->setRiskyAllowed(true) in the config, and understand you're opting into behavioural edits.{. Pair with no_extra_blank_lines and blank_line_before_statement in the same config, otherwise the guard produces style churn that reviewers start ignoring.--dry-run, fix exits 0 after silently deleting code. CI must use --dry-run; the human workflow uses plain fix. Mixing these up is how a guard becomes a no-op.--using-cache=no in CI, or a stale cache will happily pass a file that still contains dd()..php_cs + Config::create() is the 2.x style; .php-cs-fixer.php + new Config() is 3.x. If Composer pinned you to 2.x, follow 2.x docs or the config will simply not load — an error that reads like the fixers are missing when they aren't.ray(), dumpDie(), Log::debug), subclass or copy AbstractFixer and mirror the token-matching logic of the existing classes — it's the cheapest way to get a custom "no debug helper in production" rule, and it's why vendoring these classes is usually better than depending on them.How can I help you explore Laravel packages today?