jacobjoergensen/laravel-fmt
Laravel FMT is a formatting package for Laravel projects. It applies consistent, configurable code style across PHP and Blade files with a single command, keeping your codebase clean and readable, reducing review noise, and enforcing standards with minimal setup. Fast and lightweight.
What this actually is. The repository ships a Prettier-style code formatter for Laravel codebases — PHP, Blade, and (as of 0.3.0) PHP/Blade/HTML/CSS/JSON/YAML/XML fenced blocks inside Markdown — exposed as an Artisan command with a namespaced config. The package description on the listing ("render common values consistently… formatting rules instead of scattering ad-hoc PHP calls") describes a presentation-value helper library, not a code formatter, and does not match the release notes, the config keys (php.notOperatorSpace, markdown.proseWrap), or the CLI surface (--check, --reporter). Evaluate from the repo and changelog, not the marketing copy. That mismatch is a discovery-friction and trust signal, and it should be corrected in any internal write-up before a stakeholder reads the listing themselves and forms a different view of the cost.
Architecture fit. Good, and deliberately shallow: Laravel-native service provider, auto-discovery, facade, Blade helpers, published config. As a require --dev dependency it does not enter the runtime request path, so it cannot affect application latency, autoload weight in production, or upgrade compatibility of your Laravel version in the way a runtime package would. The architectural question it does touch is CI topology — it proposes collapsing Pint (PHP) + Prettier and prettier-plugin-blade (Blade/CSS/JSON/YAML) + manual Markdown discipline into one binary, one committed config, one CI step. That is a genuine reduction in moving parts, and it is the only reason to look at this package: Pint already solves PHP formatting and is Laravel-official. If PHP is your only pain, stop here.
Integration feasibility. Mechanically trivial — install pinned, publish config, commit it, run --check. The real cost is not installation, it is the reformat itself and the review capacity to absorb it. Feasible in a sprint as a spike; not feasible as a same-sprint "we now have a formatter" announcement.
Technical risk.
@php and <?php ?> blocks, broken parsing of "{$a->f("x")}", layout shifting on a second run. Good sign for a beta; also a direct statement that 0.3.1 will change output on files you already formatted. Pin an exact version; treat upgrades as scheduled work, not composer update collateral.--reporter gitlab --check emits a Code Quality report so violations annotate the MR diff. For GitHub Actions, Bitbucket, Jenkins or Azure DevOps shops this is a non-event — the single strongest capability in the release does not apply, and the adoption case degrades to "report-only log output," which is much weaker. Establish your SCM platform before scoping anything.Key questions to close before committing budget.
--stdin / staged-paths mode, and is there any editor or format-on-save integration story? Without it, local ergonomics stay at composer fmt and hook viability drops.Stack fit. Strong for a Laravel monolith or multi-repo estate where PHP, Blade and front-end assets are colocated and currently carry separate Pint and Prettier configs plus separate CI steps. Weak fit if you are PHP-only (use Pint), GitLab-only is a prerequisite for the reviewer benefit, and absent entirely if your team has no appetite for a repo-wide reformat this quarter.
Migration path. Deliberately incremental and reversible:
composer require --dev jacobjoergensen/laravel-fmt:0.3.0. No caret range during beta.php artisan fmt --check across the whole repo without writing. This produces the number that decides everything: how much of the codebase disagrees. On a mature app it will be large, and that number is the deliverable of step one.resources/views/components, app/Http/Controllers. Blade is the best day-one win because almost nobody hand-formats Blade consistently, so the diff is the most obviously correct one you will get..git-blame-ignore-revs. Reviewers skip it; revert is one command; git blame survives.Compatibility.
php.*, markdown.*, and equivalents), so a single committed file governs PHP, Blade, Markdown and asset formatting consistently. Use that: php.notOperatorSpace also governs Blade echoes and Alpine ! spacing, so set it once globally rather than per file type.docs/ and README.md because a fenced block that fails to parse is preserved exactly as written. Pseudo-code, // ... elisions and intentionally broken examples survive — no blocklist to maintain.--check will flag those files forever.Sequencing against the delivery calendar. This is a spike candidate, not a dependency. Do not attach it to a feature milestone.
| Phase | Scope | Exit gate |
|---|---|---|
| 0 — Discovery | 1 engineer, ~1 day. Confirm SCM fit, pinned version, publish config, run --check on full repo |
Repo-wide mismatch count recorded; config committed |
| 1 — Non-blocking CI | --check in one repo, report-only, plus --reporter gitlab if applicable |
Job green-or-informational; artifact filename verified on first run |
| 2 — Measurement | Two weeks of real MRs | Diff noise and review-comment volume quantified |
| 3 — Reformat | One owned directory → wider, isolated commit + blame-ignore | git diff empty on a second run (idempotency proven) |
| 4 — Enforcement | Pre-commit hook, then blocking gate | Hook survives a full sprint without being disabled |
| 5 — Revisit | At 1.0, or at a patch release that fixes idempotency bugs found in phase 3 | Build-vs-buy and consolidate-vs-Pint decision |
The order is not optional. A hook installed before the repo is clean fights every commit and gets disabled inside a week — and a disabled hook is worse than no hook because it looks like enforcement on paper.
Maintenance. Low in steady state, spiky at upgrade. The package is a dev dependency; there is no server to patch, no migration, no runtime surface. The recurring cost is the upgrade-and-reformat ritual: bump version on a branch, run formatter twice, assert git diff is empty, then land bump and reformat separately. Budget a half-day per minor release, and do not fold it into unrelated work. Also budget the config file as a reviewed artifact — it is the actual policy document, and changes to it should go through code review like any other behavioural change.
Support and bus factor. Single maintainer, 13 stars, zero dependents, MIT. There is no vendor, no SLA, no community to escalate to. Mitigations: pin exactly, keep the reformat commits isolated so an abandon-and-revert is a one-day operation, and record the fallback (Pint + Prettier + prettier-plugin-blade) in the decision log at the time of adoption — not after it becomes necessary. Do not build in-house: a Blade-aware formatter is multi-quarter work with a permanent tail (Blade evolves; the Alpine ! spacing rule and the unquoted-attribute <?= ?> parsing bug in this very release are the kind of long-tail parse defects you would be signing up to own forever). Formatting is not a differentiator; borrow, contribute upstream if the maintainer is responsive, and spend engineering budget on the roadmap.
Scaling. Two dimensions.
Failure modes and detection.
| Failure | Symptom | Detection | Mitigation |
|---|---|---|---|
| Non-idempotent output | Second run produces a new diff | Double-run assertion on your own sample | Blocks phase 4; report upstream with file + second-run diff |
| Behaviour change on patch/minor | Whole files churn on version bump | Isolated bump commit; review the reformat diff | Exact pin; scheduled upgrades |
| Formatter can't parse a file | --check red forever, unfixable |
Phase 0 full-repo scan | Exclude the path; do not chase |
Comment loss in Blade @php blocks |
Silent deletion in diffs | Targeted diff review of files with inline comments in Blade PHP blocks | Review before merge; do not trust a clean full-file run |
| GitLab artifact mismatch | Annotations never appear | Verify filename on first CI run | Don't assume; confirm from the artifact |
--reporter used without --check |
Silent no-op in write mode | N/A | Document that the two flags are a pair |
| Hook disabled by the team | "Enforcement" that enforces nothing | Hook disabled-rate over one sprint | Sequence enforcement after cleanup only |
| Nobody reads the CI job | Green, ignored | Two-week review-comment measurement | Promote to blocking only after measurement shows signal is consumed |
Ramp-up. Cheap for developers, expensive for reviewers.
composer.json scripts as fmt and fmt:check so it is boring and discoverable; document it in the PR template, not a wiki page..git-blame-ignore-revs + a two-line MR description ("formatter output only, no logic change") is the difference between a smooth rollout and a week of archaeology. Without it you poison git blame for months and pay the cost again in every subsequent review.README.md, docs/ and PR templates removes the recurring "this snippet no longer matches the API" review comment, and the parse-failure-preserves-input behaviour means no blocklist maintenance. Low effort, visible developer-experience win, and a good early proof point for stakeholders.Bottom line for the decision record. Fund a scoped spike, not an adoption. Success criteria: repo-wide mismatch count measured, idempotency verified on our own Blade templates, GitLab (or not) applicability confirmed, and two weeks of MR data showing the signal is consumed. Revisit consolidation at 1.0. The realistic upside is retiring one config and one CI step per Laravel repo and getting Blade and Markdown coverage as a side effect; the realistic downside is a beta tool that reformats files on a patch bump, which is why the pin, the isolated commit, and the documented fallback are the whole risk posture.
How can I help you explore Laravel packages today?