PHPStan: ошибки PHP до запуска — мастерская и 8 практических задач
Реальный вывод PHPStan до и после исправлений: типы, null, массивы, generics, baseline и CI. Восемь задач, сообщения со строками и исходники.

PHPStan на практике: реальные ошибки до и после, типы, null, массивы, generics, baseline и CI. Восемь задач, девятнадцать вариантов и исходники Windows/Linux.
PHPStan: найти проблему раньше, чем её встретит пользователь
В PHP-приложении ошибка часто появляется далеко от места изменения. Разработчик переименовал метод, передал число вместо строки или забыл, что поиск может вернуть null. Редкая ветка выполняется только у одного клиента, поэтому обычная ручная проверка главной страницы её не замечает. Статический анализ помогает получить часть таких сообщений ещё до запуска приложения.
PHPStan читает исходники, сопоставляет типы и проверяет вызовы по доступной ему информации. Он не ходит по пользовательским сценариям и не заменяет тесты. Его практическая польза — ранняя обратная связь при изменениях: неверный аргумент, неизвестное поле, потерянный тип коллекции или небезопасное обращение к результату разбора JSON.
В статье восемь задач и девятнадцать вариантов реального CLI. В мастерской можно сравнить код до и после, выбрать сообщение, перейти к строке и скачать исходники. Отдельно покажем baseline, уровни и случай, когда анализатор возвращает exit 0, а выполнение всё ещё заканчивается TypeError.
Где применять: API, старый проект, импорт и ежедневное ревью
Для API полезны проверки контрактов между обработчиками, сервисами и DTO. Изменение типа поля в одном месте должно обнаруживаться там, где старый код продолжает использовать прежнее значение. В импорте важны формы массивов и проверка mixed после json_decode. В фоновых задачах помогает анализ веток, которые сложно воспроизвести обычным просмотром сайта.
В старом проекте инструмент можно внедрять постепенно: сначала настроить автозагрузку и область анализа, затем выбрать уровень, разобрать понятные сообщения и определить порядок работы с оставшимся долгом. Это обычно полезнее, чем сразу добиться красивого нуля широкими исключениями. Ноль сообщений имеет смысл только вместе с известными правилами проверки.
При ревью анализатор снимает часть механической работы с человека, но смысл операции остаётся предметом обсуждения. Правильный тип суммы не гарантирует правильную формулу, а существующий метод удаления не доказывает наличие разрешения на удаление. Используйте отчёт как дополнительный источник проверяемых фактов, а не как автоматическое одобрение изменения.
Версия проекта, Composer и целевая версия PHP
В архиве установлен PHPStan 2.3.5; версия закреплена в composer.lock. Для исходников лаборатории выбран PHP 8.2 и новее. Настройка phpVersion: 80200 описывает целевую версию анализируемого кода, а PHP_VERSION в записи показывает интерпретатор, который реально запустил инструмент. Это разные параметры, их нельзя подменять друг другом.
PHPStan установлен как dev-зависимость. Для анализа её нужно установить вместе с остальными зависимостями разработки; install –no-dev исключит сам инструмент. На этапе упаковки production-приложения dev-зависимости можно убрать отдельно, после завершения проверок. Подробности команд и lock-файла есть в нашей статье о Composer.
Для нового проекта используется require –dev, для скачанного — install. Не обновляйте весь набор случайно перед первым воспроизведением. Сначала получите результат на зафиксированной версии, затем рассматривайте обновление анализатора как отдельное изменение с повторным просмотром диагностик.
composer require --dev phpstan/phpstan
# Для скачанного архива:
composer install --no-interaction --no-plugins --no-scripts
php vendor/bin/phpstan --version
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 cases/methods/before.php
Мини-мастерская: настоящие сообщения, строки и exit code
Публичная мастерская показывает записи настоящего запуска PHPStan на Windows и Linux. Анализатор не загружен в браузер и не исполняет введённый посетителем PHP на сайте. Вы выбираете заранее подготовленный вариант: до исправления, после него, уровень проверки или подключение baseline. Каждая карточка связана с командой, исходниками и реальным кодом завершения процесса.
Нажмите сообщение: откроется соответствующий файл и подсветится строка. Поле поиска фильтрует текст и identifier. Внизу можно посмотреть JSON stdout, stderr, команду генерации baseline и хеши файлов. В публичных записях абсолютный путь рабочего каталога заменён на [project], а окончания строк приведены к LF; диагностика и номера строк сохранены.
Полезный маршрут начинается с неизвестного метода, продолжается null и JSON, затем переходит к baseline и ignore. Последние случаи объясняют, почему одинаковый exit 0 может получиться по разным причинам. Для собственного исходника скачайте проект и выполните команду локально: это уже будет новый анализ вашего файла.
Что именно проверяет статический анализ
Анализатор сопоставляет объявления функций, типов, свойств, PHPDoc и возможные ветки программы. Если функция обещает строку, но возвращает число, это можно заметить без вызова функции. Если значение может быть null, обращение к свойству требует дополнительного основания. Чем точнее контракт, тем полезнее вывод инструмента.
Но анализатор не получает автоматически все знания о приложении. Динамические контейнеры, ORM, магические методы и внешние данные требуют правильной настройки, расширений или явного описания. При сообщении «класс не найден» сначала проверьте автозагрузку, а не скрывайте ошибку. Неправильная конфигурация способна породить много вторичных сообщений.
Статический анализ не выполняет обычный бизнес-сценарий и не проверяет состояние базы, доступность API или результат отправки письма. Во время работы инструмента могут загружаться его конфигурация, автозагрузка и расширения, поэтому запускать анализ неизвестного проекта следует в подходящем изолированном окружении. Наша лаборатория анализирует только собственные фиксированные файлы.
Опыт 1. Неизвестный метод и неверный аргумент
В первой задаче класс Client имеет метод rename, принимающий string. Ошибочный вариант вызывает renmae с опечаткой, затем передаёт число в правильный метод. PHPStan выдаёт две диагностики: method.notFound и argument.type. Нажатие на каждую в мастерской открывает соответствующую строку, поэтому сообщение связано с конкретным действием, а не с абстрактным количеством ошибок.
Исправление меняет имя вызова и передаваемое значение. После этого анализ того же небольшого контракта завершается без сообщений. Заметьте: мы не расширяем параметр до mixed только ради исчезновения предупреждения. Если операция действительно ожидает имя, её тип должен продолжать выражать это ожидание.
Подобные ошибки полезно ловить при рефакторинге сервиса, переименовании метода или изменении DTO. Человек может не открыть редкую форму после каждого изменения, а статическая проверка просмотрит все включённые файлы. При этом корректное имя метода ещё не доказывает, что вызов уместен по бизнес-правилам.
final class Client {
public function rename(string $name): void {}
}
function update(Client $client): void {
$client->renmae('Анна'); // method.notFound
$client->rename(42); // argument.type
}
// Исправленный вариант вызывает rename('Анна') и rename('Клиент 42').
Как читать сообщение: путь, строка, identifier и причина
Сначала откройте файл и строку, затем прочитайте ожидаемый и фактический тип. Identifier помогает понять семейство проверки: например, argument.type относится к аргументу, а return.type — к возвращаемому значению. Номер строки указывает место, где проявилось противоречие, но причина иногда находится раньше: неверный PHPDoc или слишком широкий тип результата другой функции.
Не исправляйте сообщения механически по одному без проверки общего контракта. Если десятки вызовов перестали подходить после изменения сервиса, полезнее сначала решить, каким должен быть новый интерфейс. Иначе можно добавить множество приведений типов, которые сделают анализ тихим, но скроют неправильные данные.
В мастерской сохраняется полный вывод, а не только русский пересказ. Это позволяет найти identifier в документации и сопоставить собственный запуск с примером. JSON удобен для автоматизации, таблица — для человека. Возможности форматов описаны в официальном руководстве по выводу.
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 --no-progress --error-format=json cases/methods/before.php
# В JSON: files → файл → messages → line/message/identifier.
# Сам JSON-отчёт не заменяет проверку exit code процесса.
Опыт 2. Nullable: проверить отсутствие перед обращением
Поиск клиента может вернуть объект или null. В ошибочном примере функция сразу читает name. На выбранном уровне PHPStan сообщает property.nonObject: путь с отсутствующим клиентом не обработан. Исправленная версия явно возвращает сообщение для null, после чего оставшаяся ветка работает с объектом.
Такое изменение одновременно помогает анализатору и улучшает поведение приложения. Оно описывает, что делать при отсутствии записи, вместо подавления предупреждения или надежды на то, что нужный объект всегда найдётся. В реальном API вместо строки может быть исключение предметной области или согласованный HTTP-ответ.
Не подменяйте проверку комментарием @var, если значение действительно может отсутствовать. PHPDoc способен сообщить анализатору желаемый тип, но не создаёт объект во время исполнения. В наших тестах исправленная функция реально вызывается и с null, и с существующим клиентом, чтобы статический вывод был дополнен проверкой поведения.
function label(?Client $client): string {
if ($client === null) {
return 'Клиент не найден';
}
return $client->name;
}
Опыт 3. Array shape: понятный контракт массива
Обычный тип array почти ничего не говорит о его содержимом. Array shape позволяет описать конкретные ключи: id, email и active, а также тип каждого значения. В лаборатории одна функция ошибается в имени emial, а вызывающий код передаёт строковый id и не передаёт email. Анализатор связывает эти действия с объявленным контрактом.
После исправления имя ключа совпадает с описанием, id становится целым числом, обязательный email присутствует. Это полезно для небольших внутренних структур, результатов репозитория или подготовленных данных шаблона. Если структура живёт долго и имеет поведение, отдельный DTO может быть понятнее массива с большим PHPDoc.
Описание shape не проверяет входной JSON само по себе. Приложение должно сначала подтвердить структуру реальными проверками или валидатором, а затем передавать данные в функцию с узким контрактом. Иначе комментарий окажется обещанием, которое не подтверждается фактическим вводом пользователя.
/** @param array{id: int, email: non-empty-string, active: bool} $client */
function address(array $client): string {
return $client['email'];
}
$email = address([
'id' => 42,
'email' => 'demo@example.invalid',
'active' => true,
]);
Списки, необязательные поля и граница PHPDoc
Для последовательного набора значений полезен list<T>: он описывает массив с последовательными числовыми ключами. Для словаря удобнее array<string, T>, а для фиксированной структуры — shape. Выбор должен отражать реальную операцию: список можно перебирать по порядку, словарь ищется по ключу, форма имеет определённые поля.
Необязательное поле и поле со значением null — разные состояния. В одном случае ключ отсутствует, в другом присутствует с пустым значением. Это особенно заметно в частичных обновлениях API: отсутствие поля может означать «не менять», а null — «очистить». Типы должны помогать сохранить это различие, а не скрывать его общим mixed.
Дополнительные типы описаны в руководстве PHPDoc Types. Начинайте с минимального точного описания. Сложная аннотация полезна только тогда, когда команда понимает её смысл и поддерживает её вместе с кодом; устаревший комментарий способен направить анализ в неверную сторону.
/** @param list<int> $amounts */
function total(array $amounts): int {
return array_sum($amounts);
}
/** @param array{title: string, note?: string|null} $input */
function saveDraft(array $input): void {
// note может отсутствовать или быть явно равным null.
}
Опыт 4. Generics: коллекция клиентов не принимает счёт
Коллекция может принимать object, но этого недостаточно, если в конкретном месте она должна содержать только Client. В примере template T связывает тип элементов конструктора, метода add и результата all. Когда коллекция создаётся с клиентом, PHPStan выводит соответствующий тип и отклоняет добавление Invoice.
Исправленный вариант добавляет другого клиента. Важно, что связь задаётся один раз в объявлении контейнера, а не повторяется произвольными @var возле каждого вызова. Такой подход пригодится для репозиториев, результатов поиска, сообщений и обёрток, которые должны сохранять тип переданных данных.
В лаборатории generics описаны через PHPDoc. PHP runtime не начинает автоматически проверять параметр T: сигнатура add по-прежнему принимает object. Если контейнер должен защищаться от внешних неконтролируемых вызовов во время исполнения, добавьте соответствующий runtime-контракт. Статическая проверка помогает при разработке, но не изменяет правила самого интерпретатора.
/** @template T of object */
final class Collection {
/** @param list<T> $items */
public function __construct(private array $items) {}
/** @param T $item */
public function add(object $item): void { $this->items[] = $item; }
/** @return list<T> */
public function all(): array { return $this->items; }
}
// Полный пример с Client/Invoice находится в исходниках.
Опыт 5. JSON: от mixed к проверенной строке
json_decode с ассоциативным режимом не гарантирует объект нужной формы. Корректный JSON может содержать null, число, список или строку. Опция JSON_THROW_ON_ERROR отвечает за ошибочный синтаксис, а не за соответствие данным вашего API. Поэтому прямое чтение title и возвращение его как string создаёт два противоречия, которые видны в записанном отчёте.
Исправленный вариант проверяет массив, наличие поля, строковый тип и непустое значение после trim. Только после этих условий возвращается строка. Анализатор понимает сужение типа, а runtime действительно отклоняет неподходящие данные. Это хороший пример изменения, которое приносит пользу и пользователю, и инструменту проверки.
Для больших форм лучше вынести валидацию в отдельный слой. На сайте есть материал о Symfony Validator без фреймворка. PHPStan и валидатор выполняют разные задачи: один анализирует код, другой проверяет конкретные данные во время работы приложения.
function titleFromJson(string $json): string {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($data) || !isset($data['title'])
|| !is_string($data['title']) || trim($data['title']) === '') {
throw new InvalidArgumentException('Expected a non-empty title');
}
return trim($data['title']);
}
Не обещать тип вместо проверки данных
Иногда хочется поставить @var с нужной структурой сразу после json_decode и продолжить работу. Это может убрать сообщения, но не меняет входной JSON. Если там число вместо строки, оно останется числом. Комментарий должен описывать уже установленный контракт, а не заменять проверку неизвестного ввода.
На границе HTTP, файла, очереди или базы сначала определите допустимые данные. Затем преобразуйте их в удобный внутренний тип: DTO, проверенный массив или объект значения. Внутренние функции смогут работать с узким контрактом и не повторять одинаковые проверки в каждой строке. Это также делает сообщения пользователю последовательными.
Наш тест parser запускает исправленный код с обычной строкой, null, пустым массивом, числом вместо title, пробелами и повреждённым JSON. Эти вызовы являются отдельными runtime-тестами проекта, а не результатом анализа PHPStan. В интерфейсе важно сохранять это разделение, чтобы читатель понимал происхождение каждого вывода.
Опыт 6. Baseline: принять старый долг осознанно
Baseline полезен при подключении проверки к существующей кодовой базе. В опыте старый файл обещает string, но возвращает int. Первый запуск показывает ошибку. Затем настоящая команда PHPStan генерирует baseline, а следующий анализ того же файла с подключённой конфигурацией завершается без сообщений. Код при этом не исправлен.
В записи baseline ограничен сообщением, identifier, путём и количеством. Третий вариант добавляет новый ошибочный файл: его нарушение остаётся видимым. Именно это поведение нужно проверить при внедрении — известный долг временно учитывается отдельно, а новые дефекты не получают автоматического разрешения.
Не воспринимайте baseline как доказательство качества старого участка. Это управляемый список исключений, который требует владельца и плана сокращения. Перед генерацией устраните проблемы конфигурации: если половина сообщений связана с ненайденными классами, сохранение такого шума только усложнит дальнейшую работу.
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 --generate-baseline=config/phpstan-baseline.neon fixtures/legacy.php
php vendor/bin/phpstan analyse --configuration=config/with-baseline.neon --level=9 fixtures/legacy.php fixtures/new-error.php
# Последняя команда возвращает 1: новая ошибка не скрыта baseline.
Как сопровождать baseline без накопления скрытых проблем
В рабочем процессе baseline создают отдельным осознанным изменением и просматривают его diff. Обычный CI должен читать готовый файл, а не пересоздавать его при каждом запуске. Иначе новое нарушение может попасть в список подавления автоматически, и проверка перестанет выполнять свою основную роль.
После исправления старой ошибки соответствующая запись должна исчезнуть или уменьшить count. В конфигурации лаборатории reportUnmatchedIgnoredErrors оставлен включённым. Это помогает заметить ненужные исключения. При обновлении анализатора формулировки и возможности проверок могут изменяться, поэтому обновление lock-файла сопровождается разбором новых диагностик.
Воспроизводимая лаборатория специально генерирует baseline при сборе записей, поскольку демонстрирует сам механизм на собственных маленьких фикстурах. Такой сценарий не следует бездумно переносить в production CI. Официальный порядок и ограничения разобраны в руководстве по baseline.
includes:
- ../phpstan.neon
- phpstan-baseline.neon
# В основной конфигурации:
# parameters:
# reportUnmatchedIgnoredErrors: true
Опыт 7. Ignore скрывает сообщение, но не исправляет вызов
В седьмой задаче функция ожидает string, а получает int при включённом strict_types. Первый вариант показывает argument.type. Во втором добавлен точечный @phpstan-ignore с identifier и пояснением. Анализатор возвращает exit 0, но отдельная настоящая runtime-проба вызывает функцию и получает TypeError. Мастерская показывает оба результата рядом.
Третий вариант передаёт строку и не содержит подавления. Он проходит анализ и возвращает ожидаемое значение при выполнении. Так становится видно, почему нельзя оценивать изменение только по числу сообщений: одинаковый зелёный результат анализатора может означать исправление причины или отключение наблюдения за ней.
Иногда исключение оправдано особенностью библиотеки или ограничением анализатора. Тогда его нужно сузить до конкретной причины, объяснить и сопровождать. Широкий ignore на весь файл или все ошибки определённого семейства способен скрыть будущие проблемы, о которых автор текущего изменения ещё не знает.
function codeToLabel(string $code): string { return 'Код: '.$code; }
function example(): string {
// @phpstan-ignore argument.type (учебное подавление, не исправление)
return codeToLabel(123);
}
// В strict_types этот вызов всё ещё вызывает TypeError.
// Исправленный вариант: codeToLabel('123').
Уровни: почему проверку нужно называть полностью
У PHPStan на дату подготовки есть уровни от 0 до 10. Они добавляют строгость постепенно; уровень 9, выбранный для большинства примеров, не является максимальным. Ноль сообщений на одном уровне нельзя автоматически переносить на другой. Поэтому запись результата должна включать версию инструмента, уровень и конфигурацию.
В восьмой задаче один и тот же файл без типов проверяется на уровнях 0 и 6. Первый запуск завершается без сообщений, второй сообщает об отсутствующих типах аргумента и результата. Исправленный вариант описывает array, list<int> и int, после чего проходит уровень 9. Это фактический результат трёх запусков, а не нарисованная шкала строгости.
В существующем проекте выбирайте темп повышения уровня по готовности команды разбирать причины. Alias max может быть удобен, но его смысл связан с установленной версией инструмента. Правила уровней и накопительный характер проверок описаны в официальном руководстве.
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=0 cases/levels/before.php
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=6 cases/levels/before.php
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 cases/levels/after.php
Конфигурация, автозагрузка и область анализа
В лаборатории phpstan.neon фиксирует целевую версию PHP, уровень, каталог кеша и проверку неиспользуемых исключений. Пути анализируемых файлов передаются явно, чтобы каждый вариант был небольшим и понятным. В рабочем проекте обычно указывают свои src и tests, а сторонний vendor не анализируют как код, который команда должна исправлять.
При этом классы зависимостей должны быть доступны анализатору через Composer или подходящее расширение. «Не анализировать vendor» не означает «не знать о существовании библиотек». Если фреймворк добавляет динамические методы или типы, проверьте поддерживаемую интеграцию и её конфигурацию, прежде чем добавлять массовые исключения.
Наш bin/ci.php проверяет семь исправленных after.php. Намеренно ошибочные before-файлы, fixtures и служебный Runner не объявляются проектом, прошедшим строгий анализ целиком. Это учебная область проверки. При переносе настройте scope своего приложения и зафиксируйте его в репозитории, чтобы разные участники запускали одинаковую проверку.
parameters:
level: 9
phpVersion: 80200
tmpDir: var/phpstan-cache
parallel:
maximumNumberOfProcesses: 1
reportUnmatchedIgnoredErrors: true
Windows: настоящий запуск и проверка кода завершения
Откройте PowerShell в каталоге распакованного проекта. После установки зависимостей выполните ошибочный и исправленный пример по отдельности. В PowerShell код последнего процесса доступен в LASTEXITCODE. В наших обычных анализах ошибочные примеры дают 1, исправленные — 0; сообщение в JSON не следует считать единственным сигналом успеха.
В командах явно используется php vendor/bin/phpstan, чтобы не зависеть от выбора оболочки для исполняемого файла Composer. Убедитесь, что PHP в PATH — тот, который вы собирались использовать. При нескольких установках php –ini помогает найти активную конфигурацию консоли.
Полный тестовый запуск повторяет девятнадцать анализов, генерирует учебный baseline и проверяет результаты. Он может занять заметно больше времени, чем один маленький файл. После завершения build-public обновляет исходники и инструкции в мастерской; сами PHP-файлы посетителя на сервер сайта не загружаются.
php -v
composer install --no-interaction --no-plugins --no-scripts
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 cases/methods/before.php
$LASTEXITCODE
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 cases/methods/after.php
$LASTEXITCODE
php tests/run.php
php bin/build-public.php
Linux: воспроизведение и разница CLI с PHP-FPM
На Linux установите PHP CLI и Composer для своего окружения, затем восстановите lock-зависимости от обычного пользователя. Команды анализа совпадают с Windows. Код завершения в Bash доступен через $?. Важно проверить его сразу после нужного процесса, иначе следующая команда заменит значение.
Версия PHP-FPM на рабочем сайте не обязана совпадать с CLI. Анализатор запускается в конкретном интерпретаторе, а phpVersion задаёт целевой язык проверяемого кода. Если приложение должно работать на нескольких версиях, одной настройки недостаточно для подтверждения всех runtime-сценариев: нужна подходящая матрица проверок.
В этой партии реальные прогоны выполнены на Windows PHP 8.3.33 и в чистом Linux-контейнере PHP 8.5.12. Сообщения нормализованы только по пути проекта и окончаниям строк. Внешний VPS, ваш pipeline, подключение базы и настройки FPM эти прогоны не разворачивают и не проверяют автоматически.
php -v
composer install --no-interaction --no-plugins --no-scripts
php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 cases/nullable/before.php
echo "$?"
php tests/run.php recording-linux.json
php bin/ci.php
php bin/build-public.php
CI: остановить выпуск и сохранить полезный отчёт
В CI анализатор должен возвращать свой настоящий exit code. Конструкция с безусловным || true превратит ошибку в успешный шаг и лишит проверку смысла. Если нужен JSON-артефакт даже при отказе, настройте публикацию отчёта с условием always средствами своей CI-системы, а не маскируйте результат анализатора.
Шаг установки должен включать require-dev. Затем выполняется анализ известной области кода на зафиксированном уровне. Для лаборатории используйте bin/ci.php: он собирает семь исправленных примеров и возвращает код реального процесса. Для своего приложения замените область на собственные каталоги и проверьте намеренно сломанный коммит, чтобы увидеть настоящий отказ pipeline.
На production VPS постоянный процесс PHPStan не требуется. Обычно проверка выполняется до развёртывания, а сайт получает уже подготовленный пакет. Если анализ запускается на сервере, используйте отдельный каталог и непривилегированного пользователя; не создавайте публичный обработчик, принимающий неизвестный код для анализа.
set -eu
composer install --no-interaction --no-plugins --no-scripts
# Для скачанной лаборатории:
php bin/ci.php
# Для вашего проекта после настройки scope:
# php vendor/bin/phpstan analyse --configuration=phpstan.neon --level=9 --no-progress --error-format=json src > phpstan-result.json
Кеш, обновление инструмента и автоматические исправления
Кеш ускоряет повторный анализ, но не является основанием пропустить проверку нового изменения. Храните его отдельно от публикуемых файлов и учитывайте зависимости окружения в настройках CI. При подозрении на расхождение сначала сопоставьте PHP, lock-файл, конфигурацию и реальную команду запуска, а не объявляйте сообщение случайным.
Обновление PHPStan может обнаружить нарушение, которое прежняя версия не замечала. Это не повод автоматически подавлять весь новый вывод. Разберите несколько представителей, проверьте корректность аннотаций и расширений, затем решите, что исправить сразу, а что оформить как ограниченный технический долг.
У версии 2.3.5 есть возможности автоматического исправления некоторых поддерживаемых диагностик. Лаборатория не применяет –fix и не переписывает примеры через него: изменения до/после подготовлены явно. Если используете этот режим в своём проекте, сначала сохраните состояние и просмотрите diff, затем повторите анализ и runtime-тесты. Подробности находятся в CLI-документации.
Что доказывают проверки лаборатории
Проверки выполняют настоящий анализатор для всех девятнадцати вариантов. Они сверяют ожидаемый exit code, JSON-формат, наличие файлов и корректность номеров строк, отдельные identifier, поведение baseline и совместный анализ исправленных примеров. Дополнительные runtime-вызовы проверяют JSON-parser, null-ветку и различие между подавлением и исправлением.
У каждой записи есть версии, время и хеши исходников. Поэтому можно связать сообщение с конкретным файлом, а не только увидеть похожий скриншот консоли. При собственном изменении исходника нужно повторно собрать запись и обновить просмотр кода, иначе старая диагностика может ссылаться на уже другую строку.
Это не аудит безопасности и не доказательство отсутствия дефектов. SQL-инъекции, авторизация, корректность расчёта, внешние сервисы и пользовательский интерфейс требуют своих проверок. PHPStan полезен как ранний слой обратной связи, который дополняет тесты и review. Честная граница результата делает инструмент надёжнее в повседневном процессе.
php tests/run.php
php bin/ci.php
php bin/build-public.php
# Публичные записи: public/recording.json и recording-linux.json.
# Исходники и baseline привязаны к sourceHashes.
Скачать исходники и внедрить проверку постепенно
Архив включает проект для PHPStan с Composer lock-файлом, восемь тем, исходники до/после, конфигурацию, настоящий baseline, CLI-runner, тесты и статическую мастерскую. Начните с одного небольшого каталога своего приложения, настройте автозагрузку и разберите сообщения до появления понятного устойчивого результата. Затем расширяйте область и строгость.
Основные источники: начало работы, уровни, baseline, точечное игнорирование, generics через PHPDoc. Они помогают уточнять поведение конкретной версии; реальные команды и результаты доступны в нашем проекте.
Хороший итог внедрения — не просто число ноль в консоли. Команда понимает, какие файлы проверяются, что означают исключения, как останавливается CI и где остаются runtime-риски. Тогда отчёт становится рабочим инструментом: сообщение ведёт к причине, исправление сохраняет контракт, а новый дефект замечается до выпуска.


