Новое в журнале. Маленькие pull request: почему их быстрее ревьюят и как резать задачу
PHP и бэкенд

Инструменты проверки качества PHP-кода: что реально встроить в CI

PHPStan, Psalm, PHP-CS-Fixer, PHP_CodeSniffer, Rector и PHPMD решают разные задачи и редко нужны все сразу. Разбираем, какой набор и уровень строгости имеет смысл в CI-пайплайне.

27 января 202610 мин чтенияПрактикам
Инструменты проверки качества PHP-кода: что реально встроить в CI
6 инструментовв одном пайплайне
1 правилоначинать с низкого уровня строгости

Инструменты проверки качества PHP-кода делятся на несколько несвязанных категорий: статический анализ типов, форматирование, обнаружение запахов кода и автоматический рефакторинг. Попытка включить все инструменты сразу на максимальной строгости обычно заканчивается сотнями ошибок в первый день и заброшенным пайплайном через месяц. Разберём, что каждый инструмент решает и в каком порядке их стоит внедрять.

Прежде чем выбирать конкретные инструменты, полезно отделить вопрос «что проверять» от вопроса «когда это должно блокировать сборку». Форматирование и явные ошибки типов имеет смысл делать обязательными с первого дня, а более субъективные метрики вроде сложности метода или числа параметров лучше оставлять предупреждением, пока команда не выработает общее мнение о допустимых порогах для своего конкретного проекта и его особенностей.

Ещё один частый источник провала внедрения: запуск всех инструментов локально у каждого разработчика без единой версии конфигурации в репозитории. Если у одного в команде PHPStan уровня 5, а у другого уровня 8 на локальной машине, результаты ревью начинают расходиться, и доверие к инструменту падает быстрее, чем успевает принести пользу. Конфигурация анализатора должна лежать в репозитории и быть одинаковой для всех, включая CI.

Статический анализ: PHPStan и Psalm

PHPStan и Psalm ищут противоречия типов до запуска кода: вызов метода на возможном null, передачу аргумента неподходящего типа, обращение к несуществующему свойству. Оба инструмента поддерживают уровни строгости, и именно выбор стартового уровня определяет, станет ли проверка частью рабочего процесса или будет отключена при первом неудобстве.

# Низкий уровень для старого проекта, чтобы начать без шока
vendor/bin/phpstan analyse src --level=2

# Для нового кода разумно сразу выше
vendor/bin/phpstan analyse src --level=8
ИнструментОсновная задачаКогда предпочесть
PHPStanСтатический анализ типов, расширяемые правилаБольшинство проектов, особенно с фреймворками через готовые расширения
PsalmСтатический анализ типов, таинты для безопасностиПроекты, где важна проверка путей данных на уязвимости
RectorАвтоматический рефакторинг и миграция синтаксисаМассовое обновление кода под новую версию PHP
Главная мысльСтатический анализатор не заменяет тесты, он находит то, что тесты даже не пытались проверить: несовпадение типов до того, как код вообще запустился.

Форматирование: PHP-CS-Fixer и PHP_CodeSniffer

Споры о расстановке скобок и отступов стоят команде рабочих часов, если их не автоматизировать. PHP-CS-Fixer приводит код к выбранному набору правил и умеет применять правки автоматически, а PHP_CodeSniffer исторически силён в отчётах о нарушениях стандарта и в написании собственных правил проверки. Для большинства проектов достаточно одного инструмента форматирования, запускать оба параллельно избыточно.

# Применить форматирование по правилам PSR-12
vendor/bin/php-cs-fixer fix src --rules=@PSR12

# Проверить без применения правок (для CI)
vendor/bin/php-cs-fixer fix src --dry-run --diff
Форматирование стоит запускать перед статическим анализом в CI, а не после. Иначе часть ошибок анализатора будет указывать на строки, которые всё равно переформатируются следующим шагом.

Поиск запахов кода: PHPMD и PHPCPD

В отличие от статического анализа типов, который почти всегда прав в своих выводах, инструменты поиска запахов кода дают рекомендации, а не окончательные истины. Длинный метод с высокой цикломатической сложностью не обязательно плох, если он описывает действительно сложный, но линейный бизнес-процесс без ветвлений состояния. Команде стоит воспринимать такие отчёты как повод присмотреться, а не как обязательную задачу для немедленного рефакторинга.

PHPMD (PHP Mess Detector) находит типичные признаки плохого дизайна: длинные методы, слишком много параметров, булевы флаги-аргументы. PHPCPD (PHP Copy Paste Detector) ищет буквально продублированные участки кода. Важно помнить, что найденное дублирование не автоматически означает нарушение DRY: иногда это совпадение синтаксиса, а не повторение знания, и решение нужно принимать по контексту, а не вслепую сливать совпавшие блоки.

  • PHPMD хорошо встраивается как предупреждение, не блокирующее сборку, на старте внедрения
  • PHPCPD полезен при работе с легаси для поиска кандидатов на рефакторинг
  • Оба инструмента дают сигнал для ревью, а не окончательный вердикт

Один нюанс часто упускают при обсуждении PHPMD и PHPCPD вместе: найденное дублирование кода не равно нарушению принципа DRY в его строгом смысле. Иногда два похожих блока описывают совершенно разные бизнес-правила, которые просто выглядят одинаково на уровне синтаксиса, и слепое слияние таких блоков по рекомендации инструмента способно создать более хрупкую конструкцию, чем исходное дублирование.

Вы в начале разбораДальше: разбор компромиссов, чек-лист и ответы на частые вопросы.

Автоматический рефакторинг: Rector

Rector способен не только находить устаревшие конструкции, но и переписывать их автоматически по заданным правилам: заменять each() на foreach, добавлять типы параметров по выведенным сигнатурам, переводить код на синтаксис конструктора с промоутированными свойствами. При миграции на новую версию PHP, например с 8.1 на 8.3, Rector закрывает значительную часть механической работы, которую иначе пришлось бы делать руками.

# Применить набор правил миграции на PHP 8.3
vendor/bin/rector process src --set=php83

# Сначала посмотреть diff без применения
vendor/bin/rector process src --set=php83 --dry-run

Отдельный вопрос, который команды часто упускают: что делать с существующими нарушениями на старте внедрения нового инструмента. Если требовать немедленного исправления всех найденных проблем, разработчики либо застрянут на недели, либо начнут массово подавлять предупреждения через игнор-комментарии, что хуже, чем отсутствие проверки вовсе, потому что создаёт иллюзию контроля без реального покрытия.

Как собрать это в CI без лишнего шума

Включение всех инструментов на полной строгости в первый день гарантированно похоронит инициативу под сотнями предупреждений, которые никто не будет читать. Рабочая стратегия: зафиксировать текущий уровень нарушений как базовую линию, запретить её рост, и постепенно повышать строгость только для новых и изменённых файлов.

  1. Подключить PHP-CS-Fixer в режиме dry-run как обязательную проверку, автоматизировав форматирование локально через git-хук
  2. Запустить PHPStan или Psalm на низком уровне, зафиксировать baseline существующих ошибок
  3. Добавить PHPMD как предупреждение, не блокирующее сборку, для постепенного знакомства команды с отчётами
  4. Поднимать уровень строгости PHPStan раз в несколько недель, а не разово
  5. Запускать Rector вручную перед плановыми миграциями версии PHP, а не как часть обычного CI

Эти инструменты дополняют друг друга, а не заменяют тесты и ревью кода: статический анализ находит противоречия типов, которые тесты обычно даже не пытаются покрыть, но ничего не скажет о том, правильно ли выбрана бизнес-логика. Хорошо настроенный набор из трёх-четырёх инструментов, внедряемых постепенно, приносит больше пользы, чем полный набор, включённый сразу и быстро заброшенный командой.

Стоит периодически пересматривать состав набора: инструмент, который год назад ловил реальные ошибки, со временем может начать генерировать в основном шум, если команда уже устранила типичные для проекта проблемы. Поддержка качества кода работает лучше как постоянный процесс с регулярной настройкой порогов, а не как разовое внедрение набора плагинов в CI.

Имеет смысл назначить в команде человека, который раз в квартал пересматривает пороги и baseline-файлы этих инструментов, а не оставлять их настройками, однажды выставленными и забытыми навсегда. Пятнадцать минут такого ревью на старте следующего спринта обычно экономят часы споров о том, почему инструмент внезапно начал ругаться на код, который раньше проходил проверку без единого замечания.

В конечном счёте ценность этих инструментов измеряется не числом подключённых плагинов, а тем, сколько реальных проблем они помогли поймать до код-ревью и до продакшена. Команда, которая внедряет их постепенно и с пониманием, получает в CI не формальность для галочки, а действительно полезный второй взгляд на каждое изменение, который со временем экономит куда больше часов, чем было потрачено на первоначальную настройку и последующую донастройку правил под особенности конкретного проекта.

Частые вопросы

Нужно ли использовать PHPStan и Psalm одновременно?

Обычно нет. Оба решают похожую задачу статического анализа типов, и выбор одного из них снижает дублирование усилий. Исключение, если команде отдельно важны таинты безопасности Psalm.

С какого уровня строгости PHPStan стоит начинать на старом проекте?

С низкого, например 2 или 3, зафиксировав текущие ошибки как baseline. Резкий переход на максимальный уровень на легаси обычно приводит к сотням нерелевантных предупреждений и отказу от инструмента.

Заменяет ли статический анализ модульные тесты?

Нет. Статический анализ находит несоответствия типов до запуска кода, а тесты проверяют корректность поведения во время выполнения. Это дополняющие друг друга проверки, а не альтернативы.

Код, который не стыдно передать

Начните с принципов, а дальше двигайтесь по рубрикам. Каждый разбор заканчивается чек-листом.

Случайный разбор