0% прочитано

Laravel Pint уже був у проєкті: чому я вирішила прибрати PHP-CS-Fixer

У моєму Laravel-проєкті вже був Pint, але паралельно залишався PHP-CS-Fixer зі своїми правилами. Розповідаю, чому я вирішила прибрати дублювання, залишити один formatter і привести весь PHP-код до єдиного стилю.

19 серпня 2026 р. 6 хв читанняЛаравель
У моєму Laravel-проєкті Laravel Pint уже був у `require-dev`, але я ним ніколи не користувалася. Форматування PHP-коду весь цей час працювало через PHP-CS-Fixer з окремим config і custom fixers. Під час чергового cleanup я вирішила розібратися, навіщо проєкту два formatter-и в залежностях і чи можна залишити один зрозумілий механізм.

Чому я взагалі повернулася до formatter-ів

Це не була ситуація, де мені терміново знадобився новий formatter. Laravel Pint уже давно був у require-dev, але фактично я його ніколи не використовувала.

Реальний formatting workflow проєкту був побудований навколо friendsofphp/php-cs-fixer: окремий .php-cs-fixer.dist.php, пакет із custom fixers і перевірка PHP-CS-Fixer у CI.

Коли я вкотре переглядала tooling проєкту, то помітила цю невідповідність: Pint уже є серед залежностей, але вся реальна робота виконується іншим formatter-ом. Тоді я й вирішила перевірити, чи справді PHP-CS-Fixer дає мені щось, чого не може покрити Pint, і тільки після цього прибирати старий механізм.

Сам факт, що Pint був установлений, ще не означав, що проєкт ним користувався. До цього cleanup єдиним реальним formatter-ом у моєму workflow був PHP-CS-Fixer.

Спочатку я перевірила, що саме робив PHP-CS-Fixer

PHP - старий 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);

Я не хотіла просто видалити цей config і сподіватися, що Pint зробить приблизно те саме. Спочатку я розібрала правила й порівняла їх із Laravel preset у Pint.

Звичайні style rules на кшталт spacing, short array syntax, casts і PHPDoc formatting окремо переносити не було сенсу, якщо потрібна поведінка вже покривається Pint.

Але серед старих правил були й такі, які я не хотіла автоматично переносити. Наприклад, @PHP81Migration - це вже не просто форматування, а migration ruleset, який може трансформувати код.

Так само я не стала переносити custom fixers, що змінюють або видаляють comments і PHPDoc content. Я вирішила чіткіше розділити відповідальність: formatter форматує код, а змістовний cleanup коду та документації залишається окремою задачею.

Що було в PHP-CS-Fixer

Що я вирішила

Звичайні style rules

Не дублювати, якщо потрібний стиль уже покриває Pint

@PHP81Migration

Не переносити в formatter config

Constructor formatting

Залишити стандартне форматування Pint

Comment/PHPDoc custom fixers

Не переносити як formatting rules

Окремий Finder scope

Не відтворювати механічно, а використовувати нормальний scope Pint

Окремий Finder scope

Видалити після завершення audit

Окремо я не переносила старий Finder один в один. У PHP-CS-Fixer, наприклад, був виключений `bootstrap`, але під час переходу я навмисно прогнала Pint по його нормальному project scope і привела до одного стилю також PHP-файли в `bootstrap`.

Після audit я вирішила залишити тільки Laravel Pint

Після порівняння я не знайшла причини продовжувати підтримувати окремий PHP-CS-Fixer workflow.

Pint уже був залежністю проєкту, Laravel preset покривав потрібний мені базовий code style, а специфічні правила старого formatter-а або вже не потребували окремої конфігурації, або взагалі не належали до formatting.

Тому замість підтримки двох інструментів я вирішила зробити Pint єдиним formatter-ом для PHP-коду.

Конфігурація Pint вийшла дуже маленькою

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

Спочатку такий `pint.json` навіть виглядав підозріло простим після великого PHP-CS-Fixer config. Але саме цього я й хотіла: почати зі стандартного Laravel preset і додавати overrides тільки тоді, коли в проєкті справді з’явиться конкретна причина.

Я б не переносила старі formatter rules автоматично лише тому, що вони вже є в config. Спочатку варто зрозуміти, навіщо кожне правило існує і чи не покриває його новий formatter без додаткової конфігурації.

Тоді я прибрала PHP-CS-Fixer з проєкту

До

PHP-CS-Fixer - фактичний formatter

.php-cs-fixer.dist.php

php-cs-fixer-custom-fixers

PHP-CS-Fixer check у CI

Laravel Pint у require-dev, але не використовується

Після

Laravel Pint - єдиний formatter

pint.json

composer format

composer format:check

Pint check у CI

PHP-CS-Fixer dependencies і config видалені

З composer.json я прибрала прямі залежності friendsofphp/php-cs-fixer і kubawerlos/php-cs-fixer-custom-fixers. Разом із ними Composer прибрав і транзитивні пакети, потрібні тільки старому tooling.

Після цього я видалила .php-cs-fixer.dist.php, старий cache і його entries у .gitignore.

Також я пройшлася по README, deployment documentation і GitLab CI, тому що не хотіла залишити ситуацію, де локально вже використовується Pint, а в документації чи pipeline все ще живе стара команда PHP-CS-Fixer.

Для мене CI тут важливий саме як продовження локального workflow: та сама перевірка має виконуватися автоматично в pipeline. Про те, як я сприймаю сам CI/CD flow і чим для мене відрізнялося його представлення в Bitbucket та GitLab, я писала окремо в порівнянні Bitbucket CI Bot і GitLab Pipeline.

Composer - команди для Laravel Pint
{    "scripts": {        "format": "pint",        "format:check": "pint --test"    }}

Я спеціально винесла команди в Composer scripts, щоб не пам’ятати точний шлях до binary і використовувати однаковий interface локально та в CI.

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

Перший реальний запуск Pint показав 244 style issues

До цього моменту Pint був у проєкті лише як dependency. Тепер я вперше використала його як реальний formatter і спочатку запустила check без автоматичних змін.

Перевірка форматування через Pint
composer format:check
Laravel Pint показує 801 PHP-файл і 244 style issues перед першим форматуванням

Pint перевірив 801 PHP-файл і знайшов 244 style issues. Це добре показало різницю між стилем, який роками підтримував PHP-CS-Fixer, і baseline, який задає Laravel Pint.

244 style issues не означали 244 помилки в коді. Це були відмінності форматування між старим PHP-CS-Fixer workflow і правилами, які застосовує Pint.

Я вирішила одразу привести весь PHP-код до одного baseline

Форматування всього PHP-коду через Pint
composer format
Laravel Pint виправив 244 style issues у 801 PHP-файлі
Після форматування Pint завершився з результатом `801 files, 244 style issues fixed`, а повторний `composer format:check` уже проходив без змін.

Через project-wide formatting diff вийшов великим: у підсумку змінилося 252 файли.

Це було очікувано. Окрім видалення старого tooling і оновлення конфігурації, Pint переформатував PHP-код у app, database, routes, config, bootstrap і tests.

Саме тому я винесла цю роботу в окрему задачу. Великий formatting diff набагато легше перевіряти, коли в ньому немає паралельно нової функціональності або business logic changes.

Після форматування я перевірила не тільки Pint

Оскільки formatter змінив велику кількість PHP-файлів, для мене було важливо переконатися, що migration залишилася саме formatting change.

Після форматування composer format:check проходив чисто, PHPStan не знаходив помилок, а Unit, Integration і Feature test suites також завершувалися успішно.

Тільки після цього я вважала migration завершеною.

Тепер у мене один зрозумілий formatting workflow

Щоденні команди Laravel Pint
composer format composer format:check

`composer format` я використовую, коли хочу автоматично привести код до потрібного стилю. `composer format:check` нічого не змінює і просто перевіряє стан codebase, тому саме він використовується в CI.

Що я залишила для себе після цього cleanup

  • Якщо dependency є в composer.json, це ще не означає, що проєкт реально нею користується.

  • Перед видаленням старого tooling варто спочатку розібрати його config.

  • Не потрібно переносити кожне старе правило в новий formatter автоматично.

  • Migration rules і cleanup comments не обов’язково мають бути частиною formatter-а.

  • Один formatter, одна локальна команда і одна CI-перевірка значно простіші в підтримці.

  • Великий project-wide formatting краще робити окремою задачею без функціональних змін.

У підсумку я не замінила один formatter іншим з нуля. Laravel Pint уже був у проєкті, просто до цього я ним не користувалася. Я перевірила старий PHP-CS-Fixer config, залишила тільки те, що справді мало сенс, прибрала зайвий tooling і зробила Pint єдиним механізмом форматування PHP-коду.