Статический анализ в PHP: рабочие приёмы с PHPStan
Как встроить PHPStan в реальный проект без недели красных ошибок: baseline, разумный уровень строгости, работа с legacy и типичные ложные срабатывания.

Вы запускаете phpstan analyse на проекте, которому пять лет, и получаете четыре тысячи ошибок. Естественная реакция — закрыть терминал и решить, что статический анализ не для вас. На самом деле проблема в том, что инструмент запущен неправильно: без baseline, сразу на максимальном уровне и на всём коде разом. Разберём, как получить пользу от анализа с первого дня, а не через месяц правок.
Речь пойдёт о PHPStan 1.x (актуальна ветка 1.11+; в 2024 году вышла ветка 2.0, часть настроек в ней переименована — об этом ниже). Psalm упомянем для сравнения, но основной инструмент здесь один. Установка PHPStan в конфигурацию CI и написание собственных правил — отдельные темы, здесь только рабочие приёмы внедрения и повседневного использования.
Что вообще ловит статический анализ в PHP
PHP — язык с динамической типизацией и историей, где массив был универсальным контейнером для всего. Статический анализатор читает код без запуска и находит то, что интерпретатор обнаружит только в рантайме, а иногда и вовсе пропустит до конкретной ветки исполнения:
- обращение к методу на
null— классический источник Call to a member function on null; - несоответствие типов аргументов и возвращаемых значений;
- обращение к несуществующему свойству или методу класса;
- мёртвый код: условия, которые всегда истинны или ложны;
- неверные типы в PHPDoc, которые расходятся с сигнатурой.
PHPStan работает на уровнях от 0 до 9 (в 2.0 добавлен уровень max как псевдоним максимального). Уровень 0 проверяет базовые вещи: существование классов, функций, методов. Уровень 9 придирается к каждому mixed.
Уровень строгости — это не оценка вашего профессионализма. Это выбор, сколько ложных срабатываний вы готовы разгребать сегодня ради ошибок, которые всплывут в проде через полгода.
Первый запуск: не начинайте с уровня 9
Главная ошибка новичков — сразу выставить максимум. На старом проекте это даёт тысячи ошибок, среди которых реальные баги тонут в шуме про недостающие PHPDoc-аннотации.
Минимальная конфигурация phpstan.neon в корне проекта:
parameters:
level: 0
paths:
- src
- tests
# исключаем сгенерированный код и вендор
excludePaths:
- src/Generated/*Запуск:
vendor/bin/phpstan analyseЕсли ошибок на нулевом уровне ноль — поднимайте до 1, снова запускайте, чините. И так далее. Так вы разбираетесь с ошибками порциями, а каждая порция осмысленна: уровень 1 — про неопределённые переменные, уровень 2 — про неизвестные методы на объектах и так далее. Точное распределение проверок по уровням смотрите в документации: phpstan.org/user-guide/rule-levels.
Baseline: заморозить старые ошибки, ловить новые
Даже двигаясь по уровням, на большом legacy вы упрётесь в сотни ошибок, которые не почините за один спринт. Здесь спасает baseline — снимок текущих ошибок, который PHPStan будет игнорировать, но новые ошибки того же типа поймает.
Генерация baseline:
vendor/bin/phpstan analyse --generate-baselineКоманда создаст файл phpstan-baseline.neon со списком всех текущих ошибок. Подключите его в основной конфиг:
includes:
- phpstan-baseline.neon
parameters:
level: 5
paths:
- srcТеперь phpstan analyse проходит чисто, потому что весь старый долг заморожен. Но если вы напишете новый код с ошибкой — анализатор её покажет. Это ключевой приём: команда сразу поднимает уровень до 5 или 6, а старый код чинит постепенно, вычёркивая строки из baseline.
Не превращайте baseline в свалку. Периодически перегенерируйте его — если вы починили часть ошибок, старые записи в файле останутся мёртвым грузом и скроют регрессии. Хорошая практика — раз в спринт запускать генерацию заново и смотреть в diff, сколько строк ушло.
Ложные срабатывания: игнорировать точечно
PHPStan иногда ошибается — чаще всего на магии фреймворков, динамических свойствах и коде, где типы приходят из внешних источников. Есть три способа заткнуть конкретную ошибку, от худшего к лучшему.
| Способ | Область действия | Когда уместно |
|---|---|---|
@phpstan-ignore-next-line | одна строка | разовое ложное срабатывание |
| ignoreErrors в конфиге по regexp | по шаблону сообщения | повторяющийся паттерн ошибки |
| PHPDoc с точным типом | исправляет причину | почти всегда лучший вариант |
Пример подавления по шаблону в phpstan.neon:
parameters:
ignoreErrors:
# Laravel-фасады PHPStan видит как статические вызовы
- '#Call to an undefined static method#'Но чаще правильнее не подавлять, а объяснить анализатору тип. Если метод возвращает объект неизвестного класса из контейнера, добавьте аннотацию:
/** @var \App\Service\PaymentGateway $gateway */
$gateway = $container->get('payment.gateway');
$gateway->charge($amount); // теперь PHPStan знает типРасширения для фреймворков
Голый PHPStan не знает про магию Laravel, Symfony или Doctrine. Без расширений вы получите гору ложных ошибок на Eloquent-моделях и сервис-контейнере. Ставьте специализированные пакеты:
- Laravel —
larastan/larastan(бывший nunomaduro/larastan); - Symfony —
phpstan/phpstan-symfony; - Doctrine —
phpstan/phpstan-doctrine; - PHPUnit —
phpstan/phpstan-phpunitдля проверки тестов.
Расширения подключаются через секцию includes в конфиге. Точные имена файлов расширений и способ подключения смотрите в README каждого пакета — они иногда меняются между мажорными версиями.
PHPStan или Psalm
Второй распространённый анализатор — Psalm от Vimeo. Оба решают ту же задачу, различия в деталях.
| Критерий | PHPStan | Psalm |
|---|---|---|
| Уровни строгости | 0–9, плавный вход | 1–8, обратная нумерация (1 — строже) |
| Baseline | есть | есть |
| Автоисправление | нет | есть (--alter) |
| Экосистема расширений | шире для фреймворков | сильные аннотации типов |
Для команды, которая внедряет анализ впервые, PHPStan обычно проще из-за плавного входа по уровням и большего числа готовых расширений. Psalm привлекателен возможностью автоматически расставлять аннотации через --alter. Держать оба в одном проекте не нужно — выберите один и настройте его строго.
Что делать дальше
После первого внедрения закрепите результат, чтобы анализ не деградировал:
- Добавьте запуск PHPStan в CI как обязательную проверку — иначе новый код с ошибками будет проходить в main.
- Раз в спринт перегенерируйте baseline и смотрите, сокращается ли он.
- Когда чистый код на текущем уровне стабилен неделю-две — поднимите уровень на единицу и повторите цикл.
- Настройте локальный запуск в pre-commit хуке на изменённых файлах, чтобы ошибки ловились до пуша.
Цель — не дойти до уровня 9 любой ценой, а держать код на том уровне, где реальные баги ловятся, а шум терпимый. Для большинства проектов это уровень 5–6 с постепенным движением вверх.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: PHPStan — Rule Levels, PHPStan — The Baseline
Частые вопросы
У меня PHPStan 2.0, а не 1.x — приёмы отличаются?
Запустил анализ, а получил тысячи ошибок. Что делать?
PHPStan ругается на код, который точно работает. Это баг?
Нужно ли добавлять phpstan-baseline.neon в git?
На каком уровне строгости остановиться?
Замедлит ли PHPStan мою сборку в CI?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.