0% прочитано

Laravel Pint Was Already in My Project: Why I Removed PHP-CS-Fixer

Laravel Pint was already installed in my project, but PHP-CS-Fixer was still running alongside it with its own rules. I decided to remove the duplication, keep one formatter, and standardize the PHP codebase with Pint.

19 серпня 2026 р. 6 хв читанняLaravel
Laravel Pint was already listed in `require-dev` in my Laravel project, but I had never actually used it. PHP formatting had always been handled by PHP-CS-Fixer with its own configuration and custom fixers. During another project cleanup, I decided to find out why I was carrying two formatters as dependencies and whether I could leave just one clear formatting mechanism.

Why I Revisited the Formatting Setup

This was not a situation where I suddenly needed a new formatter. Laravel Pint had already been in require-dev for quite a while, but I had never actually used it.

The real formatting workflow was built around friendsofphp/php-cs-fixer: a separate .php-cs-fixer.dist.php, a custom fixers package, and a PHP-CS-Fixer check in CI.

While reviewing the project's tooling, I noticed the mismatch: Pint was already a dependency, but all actual formatting was done by another tool. That was when I decided to check whether PHP-CS-Fixer was still giving me anything I could not reasonably cover with Pint before removing the old setup.

Having Pint installed did not mean the project was using it. Before this cleanup, PHP-CS-Fixer was the only formatter that was actually part of my workflow.

I Started by Checking What PHP-CS-Fixer Was Actually Doing

PHP - old PHP-CS-Fixer config
<?php $finder = (new PhpCsFixer\Finder())    ->in(__DIR__)    ->exclude(['bootstrap', 'storage', 'public', 'node_modules', 'vendor']); return (new PhpCsFixer\Config())    ->registerCustomFixers(new PhpCsFixerCustomFixers\Fixers())    ->setRules([        '@PSR12' => true,        '@PHP81Migration' => true,        PhpCsFixerCustomFixers\Fixer\ConstructorEmptyBracesFixer::name() => true,        PhpCsFixerCustomFixers\Fixer\NoUselessCommentFixer::name() => true,        PhpCsFixerCustomFixers\Fixer\PhpdocNoIncorrectVarAnnotationFixer::name() => true,        'binary_operator_spaces' => [            'default' => 'single_space',            'operators' => [                '=' => 'single_space',                '=>' => 'single_space',            ],        ],        'not_operator_with_successor_space' => true,        'cast_spaces' => true,        'array_syntax' => ['syntax' => 'short'],        'phpdoc_scalar' => true,        'phpdoc_align' => [            'align' => 'left',        ],    ])    ->setFinder($finder);

I did not want to delete this config and simply assume Pint would do roughly the same thing. I reviewed the rules first and compared them with Pint's Laravel preset.

Regular style rules such as spacing, short array syntax, casts, and PHPDoc formatting did not need to be copied manually when the required behavior was already covered by Pint.

Some rules were different. @PHP81Migration, for example, is more than formatting: it is a migration ruleset that can transform code.

I also chose not to carry over custom fixers that modify or remove comments and PHPDoc content. I wanted a clearer boundary: the formatter should format code, while semantic code or documentation cleanup should remain a separate concern.

What existed in PHP-CS-Fixer

What I decided

Regular style rules

Do not duplicate them when Pint already covers the required style

@PHP81Migration

Do not move it into formatter configuration

Constructor formatting

Keep Pint's standard formatting

Comment/PHPDoc custom fixers

Do not preserve them as formatting rules

Separate Finder scope

Do not reproduce it mechanically; use Pint's normal project scope

.php-cs-fixer.dist.php

Remove it after the audit

I also did not reproduce the old Finder scope one-to-one. PHP-CS-Fixer excluded `bootstrap`, for example, while during the migration I intentionally ran Pint across its normal project scope and brought the PHP files in `bootstrap` to the same style as well.

After the Audit, I Decided to Keep Only Laravel Pint

After comparing the setups, I no longer had a good reason to maintain a separate PHP-CS-Fixer workflow.

Pint was already a project dependency, the Laravel preset covered the baseline code style I needed, and the specific rules in the old formatter either did not require separate configuration anymore or did not really belong to formatting.

So instead of maintaining two tools, I decided to make Pint the single formatter for the PHP codebase.

The Pint Configuration Ended Up Being Very Small

Laravel Pint - pint.json
{    "preset": "laravel"}

At first, this `pint.json` looked almost suspiciously small compared with the old PHP-CS-Fixer config. But that was exactly what I wanted: start with the Laravel preset and only add overrides when the project has a real reason for them.

I would not migrate old formatter rules automatically just because they already exist in a config. It is worth understanding why each rule is there and whether the new formatter already covers the same intent.

Then I Removed PHP-CS-Fixer from the Project

До

PHP-CS-Fixer — actual formatter

.php-cs-fixer.dist.php

php-cs-fixer-custom-fixers

PHP-CS-Fixer check in CI

Laravel Pint in require-dev, but unused

Після

Laravel Pint — the only formatter

pint.json

composer format

composer format:check

Pint check in CI

PHP-CS-Fixer dependencies and config removed

I removed the direct friendsofphp/php-cs-fixer and kubawerlos/php-cs-fixer-custom-fixers dependencies from composer.json. Composer also removed the transitive packages that had only been required by that tooling.

Then I deleted .php-cs-fixer.dist.php, the old cache file, and its entries in .gitignore.

I also updated the README, deployment documentation, and GitLab CI. I did not want a half-finished migration where Pint was used locally while old PHP-CS-Fixer commands were still documented or executed in the pipeline.

CI matters to me here as an extension of the local workflow: the same check should run automatically in the pipeline. I wrote separately about how I see that CI/CD flow and how its presentation differed for me in Bitbucket CI Bot and GitLab Pipeline.

Composer - Laravel Pint commands
{    "scripts": {        "format": "pint",        "format:check": "pint --test"    }}

I intentionally exposed the commands through Composer scripts so I would not need to remember the exact binary path and could use the same interface locally and in CI.

YAML - Laravel Pint in GitLab CI
pint:  extends: .php_job   stage: quality   script:    - composer format:check

My First Real Pint Run Found 244 Style Issues

Until this point, Pint had only existed in the project as a dependency. This was the first time I actually used it as the formatter, and I started with a check that did not modify any files.

Check formatting with Pint
composer format:check
Laravel Pint reports 801 PHP files and 244 style issues before the first formatting pass

Pint checked 801 PHP files and found 244 style issues. That gave me a clear picture of the difference between the style that had been maintained by PHP-CS-Fixer and the baseline applied by Laravel Pint.

Those 244 style issues were not 244 code errors. They were formatting differences between the previous PHP-CS-Fixer workflow and the style applied by Pint.

I Decided to Bring the Whole PHP Codebase to One Baseline

Format the whole PHP codebase with Pint
composer format
Laravel Pint fixed 244 style issues across 801 PHP files
After formatting, Pint finished with `801 files, 244 style issues fixed`, and the next `composer format:check` completed without any changes.

Because this was a project-wide formatting pass, the diff was large: 252 files changed in total.

That was expected. In addition to removing the old tooling and updating configuration, Pint reformatted PHP code across app, database, routes, config, bootstrap, and tests.

That is why I kept this work in a separate task. A large formatting diff is much easier to review when it is not mixed with new functionality or business logic changes.

I Verified More Than Just Pint Afterwards

Because the formatter touched a large number of PHP files, I wanted to make sure the migration remained a formatting-only change.

After the formatting pass, composer format:check was clean, PHPStan reported no errors, and the Unit, Integration, and Feature test suites also completed successfully.

Only after those checks did I consider the migration finished.

Now I Have One Clear Formatting Workflow

Daily Laravel Pint commands
composer format composer format:check

I use `composer format` when I want Pint to update the code automatically. `composer format:check` does not modify anything and only validates the current state, so that is the command I use in CI.

What I Took Away from This Cleanup

  • A dependency being present in composer.json does not mean the project is actually using it.

  • Audit the old tooling configuration before removing it.

  • Do not automatically move every old rule into the new formatter.

  • Migration rules and comment cleanup do not necessarily belong in a formatter.

  • One formatter, one local workflow, and one CI check are much easier to maintain.

  • Keep project-wide formatting changes separate from functional changes.

In the end, I did not introduce Pint from scratch. It was already in the project; I simply had never used it. I audited the existing PHP-CS-Fixer configuration, kept only what still made sense, removed the redundant tooling, and made Pint the single formatting mechanism for the PHP codebase.