Строгие типы везде
Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 Evidence: Code-backed PHPStan: 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 proposed New or modified engine code.
- Level 10 analysis Strictest PHPStan level over src/, treatPhpDocTypesAsCertain on.
- Zero-error gate No source baseline; unmatched ignores also fail.
- Strict profile level: max; no new ignore entries permitted.
- Redesign, not suppress If it cannot be expressed honestly, the design changes.
treatPhpDocTypesAsCertain — часть этого подхода. Аннотации PHPDoc рассматриваются как непреложная истина, поэтому @param list<T> или @return non-empty-string — это не комментарий, который анализатор вежливо игнорирует, а проверяемое обещание. Аннотация и тип во время выполнения должны совпадать.
Что говорят свидетельства
Заголовок раздела «Что говорят свидетельства»Эта страница имеет статус Evidence: Code-backed . Доказательством служит сама конфигурация:
phpstan.neon.distзадаётlevel: 10,phpVersion: 80400, анализируетsrcи не содержит ключаbaseline:— для анализа исходного кода нет файлаphpstan-baseline.neon.- Тот же файл задаёт
treatPhpDocTypesAsCertain: trueиreportUnmatchedIgnoredErrors: true, со встроенным примечанием о том, что анализ исходного кода на L10 зафиксирован на нуле ошибок и любая регрессия должна приводить к сбою CI. - Каждая из оставшихся
ignoreErrorsограничена поidentifierи часто поpath, с комментариями, объясняющими обоснование мягкой зависимости и цели рефлексии; это не массово сгенерированная базовая линия. phpstan-strict.neon.distнаследует эту конфигурацию, повышает уровень доmaxи замораживает список игнорирования, поэтому в строгом профиле нельзя добавить ни одной новой записи.
Связь со стандартами здесь прямая. Движок должен создавать файлы, по которым программа чтения может перемещаться от трейлера и таблицы перекрёстных ссылок, согласно Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 . Точные байтовые смещения являются проблемой типов ещё до того, как станут проблемой сериализации. Смещение — это целое число, которое никогда не должно молча стать чем-то иным. Конвейер, который типобезупречен на Level 10, уже устранил большинство способов, которыми арифметика может незаметно дать сбой.
Практический пример
Заголовок раздела «Практический пример»Строгая типизация особенно заметна там, где правило предметной области закодировано как тип, а не как проверка во время выполнения. Дискриминатор соответствия отвечает на вопросы уровня спецификации с помощью исчерпывающего 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— настройка, которая приводит к сбою сборки, когда запись игнорирования больше ничему не соответствует, предотвращая устаревшие подавления.- Проектное давление — эффект ограничения, которое вынуждает писать код определённым образом, в отличие от проверки, которая лишь его измеряет.