zenphp/fixr
Fixr by ZenPHP is a Laravel/PHP package that provides a simple, developer-friendly way to handle common fix-up tasks, with straightforward setup and minimal configuration for smoother project maintenance.
Because fixr ships as a zero-dependency, dev-only formatter, the fastest path is:
composer require --dev zenphp/fixr
Then inspect three things before writing any config:
composer show zenphp/fixr and look under bin to confirm the executable (vendor/bin/fixr is the convention for PHP tools, but verify — the package has no dependents and no prior art to lean on).fixr.php, fixr.json, or a .fixr file at the project root. "Opinionated" almost always means a small, fixed rule set; if you find a config file with twenty toggles, that's a red flag.src/Rules/ directory will tell you what it actually enforces. If it only ships PSR-12 + a handful of stylistic opinions, treat it as a lighter Pint, not a replacement for PHPStan/Psalm.Your first practical use case is a single-file sanity check before touching the repo:
vendor/bin/fixr --dry-run app/Http/Controllers/UserController.php
--dry-run (or --check, whichever the binary exposes via --help) tells you whether it will rewrite the file. If the tool reformats one file without surprises, add the same flags to composer.json:
"scripts": {
"format": "fixr",
"format:check": "fixr --dry-run"
}
Run composer format:check in CI and composer format locally. That's the entire minimum viable integration — one script pair, one CI step, done.
Composer-script-first, artisan-second. Do not assume this package ships a Laravel service provider or an artisan fixr command. Most opinionated formatters are CLI-only by design (Pint's artisan pint wrapper is the exception, not the rule). If you need an artisan command, write a three-line wrapper yourself:
Artisan::command('format', fn () => passthru(base_path('vendor/bin/fixr')));
Stage the rollout on legacy codebases. "Opinionated" means it will rewrite nearly every file on first run. Do not open a 900-file formatting PR on a production repo. Use one of these:
fixr app/) and merge directory-by-directory..git-blame-ignore-revs after the initial commit so git blame stays readable.Wire it into a pre-commit hook, not a commit-msg hook. With lefthook, husky, or a raw .git/hooks/pre-commit:
#!/bin/sh
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')
[ -z "$STAGED" ] && exit 0
vendor/bin/fixr $STAGED && git add $STAGED
Pin the tool version in CI. Because the ruleset is opinionated and the release cadence is unclear (last release stamped 2025-10-24, zero stars → likely a single-maintainer project), a minor version bump can silently reformat the whole repo. Use composer require --dev zenphp/fixr:^1.0 and composer.lock as your actual pin. In CI, composer install (not update) so the lockfile wins.
Pair it with a PHP version target. Most formatters infer syntax from composer.json's require.php constraint. If your codebase says "php": "^8.1" but you actually use 8.3 features, the formatter may strip arrow-function syntax or fail to parse. Verify the formatter respects the constraint or accepts an explicit --php-version flag.
Editor integration via file watcher. If the package doesn't ship a PHPStorm/VSCode plugin (and it almost certainly doesn't — no stars, no dependents), use a raw file watcher on save calling fixr $FilePath$. This is the single biggest day-to-day win and takes five minutes to configure.
Do not run this alongside Pint without deciding who wins. Both tools operate on the PHP-CS-Fixer ruleset in spirit. If Pint runs on save and fixr runs on pre-commit, you will get a ping-pong diff war: Pint formats, fixr reformats differently, commit fails, you re-run Pint, and the cycle repeats. Pick one. If you're adopting fixr, remove Pint from composer.json and delete pint.json.
Opinionated = limited escape hatches. If you find yourself wanting to configure brace style, alignment, or import ordering, that's a signal to use Pint or raw PHP-CS-Fixer instead. The value proposition here is zero configuration; fighting it defeats the purpose.
--dry-run exit codes are the CI contract. Confirm the tool exits non-zero when files would change; many formatters default to exit 0 and require --test or set -o pipefail to actually fail the build. Test this in a scratch branch before trusting it.
Formatting generated code and vendor directories. Add an ignore file (typically .fixrignore, but verify) covering vendor/, storage/, bootstrap/cache/, *.blade.php, and any migrations your ORM generates. Reformatted generated files create noise and occasionally break code-generation diffs.
Blade templates are almost never supported. Formatters for PHP parse PHP; .blade.php is a different grammar. Don't expect fixr to touch them, and don't be surprised when a fixr run on resources/views/ silently skips everything.
Debugging a weird reformat. If a file reformats unexpectedly, run fixr --diff (or redirect the output) instead of applying it. If the tool lacks a diff mode, git stash the result and inspect with git diff. Report the case upstream — this is an early-stage package and idiosyncratic formatting is where bugs live.
Watch for line-ending churn. If the package was developed on Linux, running it on a Windows checkout with core.autocrlf=true can produce whole-file diffs where every line "changed." Set .gitattributes to normalize PHP files to LF before the first formatting run.
Don't trust the release date. The 2025-10-24 timestamp is in the future relative to most audit contexts, which suggests either a scheduled/tagged-but-unreleased version or a metadata artifact. Check composer show --all zenphp/fixr for the actual tagged version and inspect the changelog before bumping. For a zero-star, zero-dependent package, pin to a specific tag rather than a floating constraint.
Missing extension points. If you need custom rules — e.g., enforcing a house style for final classes or strict types — assume fixr does not support plugins until the README says otherwise. The realistic extension point is to run fixr first and PHP-CS-Fixer with your custom rules second, accepting that the second pass must be a no-op on fixr-formatted code. Verify that empirically before committing to the pipeline.
How can I help you explore Laravel packages today?