gossi/php-code-formatter
gossi/php-code-formatter is a PHP library for formatting PHP source code. It parses code into a token stream and applies configurable formatting rules, letting you enforce a consistent style programmatically or from the command line.
Install it like any other Composer dependency:
composer require gossi/php-code-formatter
The repo is listed as unknown here, so before writing code, spend five minutes in three places:
README.md — the canonical usage snippet and any documented config options.src/ — the entry class (CodeFormatter) and the Parser/Printer pair; that's the whole mental model of the package.tests/ — fixtures are the fastest way to see exactly what input/output the formatter promises, including edge cases the README glosses over.The minimal first use case is formatting a string in-process, which is the one thing this package gives you that Pint and PHP-CS-Fixer do not (they shell out to a binary):
use gossi\CodeFormatter\CodeFormatter;
$formatter = CodeFormatter::create(); // verify the factory/method name in src/
$pretty = $formatter->format($dirtyPhpCode);
file_put_contents($path, $pretty);
Validate it against a single real file from your codebase before touching anything else — commit first, run it, and git diff. A formatter is only trustworthy once you've seen it round-trip your own code unchanged. If the diff on already-PSR-2 files is empty, you have a sane baseline; if it rewrites unrelated lines, stop and reconcile before scaling up.
Format-on-write wrapper. The most useful Laravel integration is a small service that wraps the formatter so it's swappable later:
namespace App\Support;
use gossi\CodeFormatter\CodeFormatter;
class PhpFormatter
{
public function format(string $code): string
{
return CodeFormatter::create()->format($code);
}
}
Bind it in a service provider, and every consumer (commands, controllers, jobs) depends on your class, not on an abandoned package. When you eventually migrate to php-cs-fixer, only this file changes.
Formatting user-submitted snippets. This is the strongest day-to-day use case in a Laravel app: a CMS or admin panel where users paste PHP into a code block, and you want to render it tidily. Doing it in-process avoids the exec()/proc_open() surface and the timeout risk of spawning a binary per request. Run it in a queued job and cache the result keyed by a hash of the input — formatting is not free, and it's deterministic.
One-off normalization of a legacy directory. Write an Artisan command that walks a path, formats each .php file, and writes it back only when the output differs:
foreach (File::allFiles($path) as $file) {
$original = $file->getContents();
$formatted = $this->formatter->format($original);
if ($formatted !== $original) {
File::put($file->getPathname(), $formatted);
$this->line("formatted: {$file->getRelativePathname()}");
}
}
The !== guard keeps the command idempotent and makes a second run a clean no-op — that's your regression test.
CI drift check. Rather than writing files, format into memory and diff against the original; exit non-zero on mismatch. This turns the formatter into a linter without a second tool:
$formatted = $this->formatter->format($original);
if ($formatted !== $original) {
$this->error("{$file->getRelativePathname()} is not formatted");
$failed = true;
}
Pre-commit hook. Pair the above with a Husky/simple-git-hooks setup that runs it on staged files only. Formatting the whole tree on every commit gets slow fast on a large Laravel app.
Formatting generated code. If you scaffold classes from stubs (custom make:* commands), running the generated output through the formatter keeps vendor/bin/pint --test green in CI without hand-tuning stub whitespace — though Pint itself can format a single file passed on stdin, so weigh this against just calling Pint.
Check the PHP version constraint first. This is an old, low-activity package. Its composer.json require.php almost certainly predates PHP 7.4 typed properties, and very likely predates enums, attributes, readonly, match, arrow functions, and constructor promotion. On a modern Laravel app (PHP 8.1+) a tokenizer-based formatter that doesn't understand the syntax can silently mangle or reorder tokens. Run it over a small sample of files that use your newest syntax before trusting it anywhere near production code.
It's a whitespace/layout formatter, not a linter. Expect indentation, spacing, and line-break normalization. Do not expect it to sort imports, add/remove declare(strict_types=1), fix brace style preferences that conflict with its presets, or report semantic issues. Pair it with PHPStan/Pint rather than replacing them.
Always operate on a clean working tree. Because formatting is a whole-file rewrite, a bad run on a dirty tree makes the real diff unrecoverable. Commit, run, inspect git diff --stat, then git diff.
Idempotency is your smoke test. format(format($x)) === format($x) should hold for every file. If it doesn't, the formatter is unstable on that construct — exclude those files rather than fighting it.
Watch line endings and tabs. If the package exposes indentation and EOL options (look for setIndentation, setLineEnding, or a config array in src/), set them explicitly rather than relying on defaults, especially in mixed-Windows teams. A formatter that defaults to \r\n produces whole-file diffs on Linux CI.
Debugging a mangled file: bisect by construct. Isolate the smallest snippet that changes incorrectly — a single match expression, an attribute, a nested closure. token_get_all() the input and compare with the output to see which token the printer dropped. The Parser→Printer pipeline means the bug is nearly always in token classification, not in the printer.
Extension point is the Printer. If you need a house style the presets don't cover, subclass the printer rather than post-processing the string with regexes — regexes over PHP will eventually corrupt something.
Prefer PHP-CS-Fixer/Pint for enforcement, keep this for embedding. For project-wide enforcement, Pint is maintained, understands modern syntax, and is what the rest of the Laravel ecosystem uses. This package's niche is formatting in-process, where shelling out is undesirable. Don't adopt it as your team's canonical style enforcer; do adopt it where you need format($string) as a function call.
Vendor it or pin it hard. With 31 stars and low activity, an accidental composer update pulling a breaking change (or a transitive bump that breaks on a new PHP minor) is a real risk. Pin the exact version in composer.json and add a note in the PhpFormatter class explaining why it's pinned.
How can I help you explore Laravel packages today?