Строгие типы везде
Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5PHPStan: Level 10, no src baseline
NextPDF запускает PHPStan на Level 10 для исходного кода движка без базовой линии подавлений. На этой странице объясняется, почему “отсутствие базовой линии” — это проектное решение, а не деталь инструментария, и что именно такая строгость даёт конвейеру, который не должен молча искажать данные.
Почему это важно
Заголовок раздела «Почему это важно»В большинстве приложений строгая типизация — это гигиена кода. В движке PDF она ближе к механизму корректности. Формат не прощает ошибок. Предполагается, что программа чтения находит содержимое, читая файл с конца — через трейлер и таблицу перекрёстных ссылок, поэтому байтовые смещения, записываемые при формировании файла, должны быть точными. Представьте тип, который незаметно расширяется до mixed, int, который молча становится string, или nullable-значение, которое разыменовывается без проверки. Любая из этих ошибок может породить файл, который нормально открывается в одной программе просмотра и не проходит проверку в другой — спустя недели и без трассировки стека, указывающей на причину.
В этой области особенно дорого обходятся именно молчаливые сбои. Строгая типизация вместе со строгим анализатором позволяет движку превращать целый класс молчаливых сбоев во время выполнения в явные ошибки на этапе сборки.
Коротко о главном
Заголовок раздела «Коротко о главном»- Исходный код движка анализируется на PHPStan Level 10 — самом строгом уровне; это закреплено в
phpstan.neon.dist. - Для исходного кода нет базовой линии подавления. Конфигурация фиксирует анализ исходного кода на нуле ошибок. Регрессия приводит к сбою сборки, а не растворяется в разрастающемся файле игнорирования.
- Те немногие записи
ignoreErrors, которые существуют, являются узкими, ограниченными по идентификатору и пути и отдельно обоснованными в конфигурации (границы мягких межпакетных зависимостей и точки сопряжения тестов с целями рефлексии), а не массовой базовой линией. - Отдельный строгий профиль работает на
level: maxи запрещает любые новые записи игнорирования, поэтому для нового кода действует ещё более жёсткий стандарт. - Предполагаемый эффект — проектное давление: код, который нельзя выразить типобезупречно, не проходит проверку; его перепроектируют, а не подавляют.
Как NextPDF подходит к этому
Заголовок раздела «Как NextPDF подходит к этому»Разница между “мы используем строгий анализатор” и “мы используем строгий анализатор без базовой линии” — в этом и заключается весь смысл, поэтому важно быть точными.
Сама базовая линия фиксирует каждое существующее нарушение и указывает анализатору игнорировать именно его. Это прагматичный способ внедрить статический анализ в унаследованной кодовой базе, но у него есть цена. Базовая линия превращается в тихий реестр долга, на который система типов согласилась не смотреть. Новые нарушения того же рода могут незаметно проскользнуть рядом с уже существующими. Обещание анализатора ослабевает с “этот код типобезупречен” до “этот код не хуже, чем был”.
NextPDF не идёт на этот компромисс для исходного кода движка. Конфигурация фиксирует анализ исходного кода на нуле ошибок и включает reportUnmatchedIgnoredErrors, поэтому даже устаревшее подавление — то, которое больше ничему не соответствует, — приводит к сбою сборки. Оставшиеся узкие игнорирования ограничены конкретным идентификатором ошибки и файлом. Каждое из них сопровождается встроенным пояснением, почему эта граница преднамеренна (например, ядро программируется на интерфейс Pro/Enterprise, от которого намеренно не зависит напрямую). Рецензент может прочитать и оценить каждое такое игнорирование. Здесь нет непрозрачного списка, над которым легко потерять контроль.
Процесс, который поддерживает эту честность:
- Change proposedNew or modified engine code.
- Level 10 analysisStrictest PHPStan level over src/, treatPhpDocTypesAsCertain on.
- Zero-error gateNo source baseline; unmatched ignores also fail.
- Strict profilelevel: max; no new ignore entries permitted.
- Redesign, not suppressIf it cannot be expressed honestly, the design changes.
treatPhpDocTypesAsCertain — часть этого подхода. Аннотации PHPDoc рассматриваются как непреложная истина, поэтому @param list<T> или @return non-empty-string — это не комментарий, который анализатор вежливо игнорирует, а проверяемое обещание. Аннотация и тип во время выполнения должны совпадать.
Практический пример
Заголовок раздела «Практический пример»Строгая типизация особенно заметна там, где правило предметной области закодировано как тип, а не как проверка во время выполнения. Дискриминатор соответствия отвечает на вопросы уровня спецификации с помощью исчерпывающего match, поэтому необработанный случай становится ошибкой типов, а не неправильным PDF:
declare(strict_types=1);
enum ConformanceMode: string{ case Plain = 'plain'; case PdfUa2 = 'pdfua2'; case PdfA4 = 'pdfa4';
/** @return 2|3|4|null */ public function pdfaPart(): ?int { return match ($this) { self::PdfA4 => 4, default => null, }; }}Аннотация @return 2|3|4|null — это не документация. При
treatPhpDocTypesAsCertain она проверяется. Вызывающий код, который предполагает, что результат всегда int, получает сообщение об этом на этапе анализа — прежде чем будет записан хотя бы один байт несоответствующего номера части PDF/A.
Распространённое заблуждение
Заголовок раздела «Распространённое заблуждение»Ловушка в том, чтобы прочитать “отсутствие базовой линии” как “так получилось, что в коде нет нарушений”. Это перевёрнутая логика. Отсутствие базовой линии — это причина, а не удачный исход. Поскольку нарушение некуда отложить, код, который его породил бы, приходится писать иначе. Level 10 без базовой линии для исходного кода — это ограничение, которое формирует проект, а не отчётная ведомость, описывающая его постфактум.
Второе заблуждение: что горстка записей ignoreErrors — это та же базовая линия под другим названием. Это не так. Базовая линия генерируется массово и непрозрачна. Эти записи написаны по отдельности, ограничены по идентификатору, снабжены пояснениями и защищены reportUnmatchedIgnoredErrors, поэтому не могут незаметно устареть.
Пределы и границы
Заголовок раздела «Пределы и границы»Эта страница посвящена анализу исходного кода движка. Набор тестов анализируется в отдельной, намеренно отличающейся области и конфигурации; “отсутствие базовой линии” здесь — это утверждение о src/, а не утверждение, что любой вспомогательный анализ в репозитории не имеет базовой линии. PHPStan доказывает типовую корректность, а не корректность поведения. Он не заменяет пирамиду тестирования, а лишь устраняет категорию сбоев, которые тестам иначе пришлось бы выискивать. Точный уровень, флаги и набор игнорирований актуальны на дату проверки этой страницы. Авторитетным источником всегда являются phpstan.neon.dist и phpstan-strict.neon.dist в основном репозитории.
Редакция не меняет эту дисциплину. Каждая редакция собирается из одного и того же исходного кода уровня Level 10:
| Edition | Availability |
|---|---|
| Core | Исходный код Core анализируется на Level 10 без базовой линии для исходного кода. |
| Pro | Pro построена на той же дисциплине исходного кода уровня Level 10. |
| Enterprise | Enterprise построена на той же дисциплине исходного кода уровня Level 10. |
Связанная документация
Заголовок раздела «Связанная документация»- Основы PHP 8.4 — возможности языка, на которые опирается система типов.
- Ошибки как функция — что происходит со сбоями, которые выявляет строгая типизация.
- Модель конвейера — архитектура, которую защищает эта дисциплина.
Глоссарий
Заголовок раздела «Глоссарий»- PHPStan Level 10 — самый строгий уровень анализа, при котором нетипизированные и слабо типизированные значения рассматриваются как ошибки, а не как предупреждения.
- Базовая линия — сгенерированная запись существующих нарушений, которые анализатор должен игнорировать. NextPDF не использует её для исходного кода движка.
treatPhpDocTypesAsCertain— настройка PHPStan, которая рассматривает аннотации типов PHPDoc как проверяемые факты, а не как рекомендательные комментарии.reportUnmatchedIgnoredErrors— настройка, которая приводит к сбою сборки, когда запись игнорирования больше ничему не соответствует, предотвращая устаревшие подавления.- Проектное давление — эффект ограничения, которое вынуждает писать код определённым образом, в отличие от проверки, которая лишь его измеряет.