API, который не угадывает
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
NextPDF заставляет вас прямо указывать, что именно вы имеете в виду. Там, где намерение меняет байты — уровень подписи, назначение вывода, целевой профиль соответствия, — это обязательный явный аргумент, а не значение, которое движок выводит из контекста.
Эта страница показывает эту позицию в исходном коде самого движка: сигнатуры методов, именованные аргументы и точки, где неоднозначный вход отклоняется до того, как будет создан хотя бы один байт.
Почему это важно
Заголовок раздела «Почему это важно»Догадка — это решение, принятое за вас и без вашего ведома. В текстовом поле это слегка раздражает. В PDF это скрытый дефект, потому что отправляемый файл часто является юридическим или архивным артефактом, корректность которого позже проверяет кто-то другой с помощью валидатора.
Рассмотрим подпись. Её дайджест вычисляется по объявленному диапазону байтов, который намеренно исключает само значение подписи ( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). API, который втихую “помогает” — переписывает структуру, выводит уровень, дополняет заполнитель, — не помог. Он изменил байты, которые подпись должна была защищать. Догадка, кажущаяся дружелюбной в месте вызова, через несколько недель становится инцидентом в продакшене. Это всё та же строка кода.
Если коротко
Заголовок раздела «Если коротко»- Если выбор меняет результат и не имеет безопасного значения по умолчанию, NextPDF делает его обязательным аргументом, а не выводит из контекста.
- Необязательные аргументы, которые читаются неоднозначно, делаются именованными, чтобы место вызова заявляло намерение (
newLine: true, а не голыйtrue). - Входные данные, которые могут быть небезопасными, проверяются до отрисовки и отклоняются типизированным исключением, называющим причину.
- Экземпляр документа предназначен для однократного использования: он строится, отдаётся и отбрасывается. Метода
reset()нет, поэтому нет и догадки “а эта штука используется повторно?”. - Движок никогда не отдаёт правдоподобно выглядящий артефакт вместо того, который вы запросили. Вместо этого он отказывается.
Как это устроено в NextPDF
Заголовок раздела «Как это устроено в NextPDF»Механизм прост, и в этом весь смысл: система типов, именованные аргументы, перечисления вместо “магических” строк и небольшое число продуманных защитных проверок, размещённых до вывода.
Таблица сопоставляет несколько неоднозначных входов. Для каждого из них она показывает, что вывела бы библиотека, которая “помогает”, и что вместо этого делает NextPDF. Каждый столбец NextPDF описывает поведение, процитированное из исходного кода ниже на этой странице.
| Неоднозначный вход | Что делает угадывающая библиотека | Что делает NextPDF |
|---|---|---|
Строка ориентации вроде "portait" | Откатывается к значению по умолчанию и всё равно выполняет отрисовку | addPage() принимает перечисление Orientation, а не строку — опечатка является ошибкой типа, а не тихим значением по умолчанию |
Голый замыкающий true в cell() | Выбирает ту булеву позицию, которую считает подразумеваемой | Булево значение именуется в месте вызова (newLine: true); неименованный литерал — это как раз тот запах, который API устраняет |
Обёртка php:// или путь с обходом каталогов в save() | ”Старается изо всех сил” и пишет куда-нибудь | Отклоняется до построения PDF типизированным InvalidConfigException, называющим ключ, значение и ожидаемый тип |
setSignature(), затем save(), пока высокоуровневый модуль подписи не подключён | Отдаёт неподписанный файл, который вызывающий считает подписанным | Выбрасывает NotImplementedException до создания байтов, называя поддерживаемый путь |
Повторное использование экземпляра Document для второй отрисовки | Угадывает, применимо ли ещё остаточное состояние | Нет reset() и нет пути повторного использования — свежий экземпляр на каждый запрос через DocumentFactory, поэтому нет остаточного состояния, о котором нужно гадать |
Намерение — обязательный аргумент. Основной контракт, PdfDocumentInterface, принимает геометрию и выравнивание как типизированные объекты-значения и перечисления, а не разрозненные примитивы:
public function addPage( ?PageSize $size = null, Orientation $orientation = Orientation::Portrait,): static;
public function cell( float $width, float $height, string $text = '', bool|string $border = false, bool $newLine = false, Alignment $align = Alignment::Left, bool $fill = false,): static;Orientation и Alignment — перечисления, поэтому вызов не может передать "portait" так, чтобы это тихо означало “по умолчанию”. Там, где значение по умолчанию есть, оно безопасное (книжная ориентация, выравнивание влево, без рамки), а не догадка о том, что вы, вероятно, хотели.
Неоднозначные булевы значения именуются в месте вызова. В примерах, которые служат фактическим справочником по API, повторяется одна и та же форма:
$document->cell(0, 15, 'Hello, NextPDF!', newLine: true);$document->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);$pdf = $document->output(dest: OutputDestination::String);newLine: true однозначно. Голый замыкающий true таким не был бы. Уровень подписи — SignatureLevel::PAdES_B_B, член перечисления, а не строка, которую движку приходится интерпретировать. Назначение вывода — OutputDestination::String, поэтому “дай мне байты, без HTTP-заголовков, без файла” заявлено явно. Это не выводится из того, был ли передан путь к файлу.
Небезопасный вход отклоняется до записи хотя бы одного байта. save() проверяет целевой путь до построения PDF:
public function save(string $path): void{ // Reject stream wrappers and null bytes if (\str_contains($path, "\0") || \preg_match('#^[a-zA-Z]+://#', $path)) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $path, expectedType: 'valid_path', ); } // Resolve the parent directory to prevent path traversal $dir = \dirname($path); $realDir = \realpath($dir); if ($realDir === false) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $dir, expectedType: 'existing_directory', ); } // ... only now is the PDF built and written atomically}Движок не “старается изо всех сил” с обёрткой php:// или путём с обходом каталогов. Он отказывается, и исключение называет ключ, значение и ожидаемый тип.
Движок отказывается, а не выдаёт вводящий в заблуждение артефакт. Самая сильная форма отказа угадывать — вообще не производить вывод, когда такой вывод был бы неправдивым. Когда высокоуровневая подпись настроена, но шов записывающего модуля, который фактически подписывал бы, не подключён, путь построения выбрасывает исключение до создания байтов, а не отдаёт неподписанный файл, который вызывающий считает подписанным:
if ($this->padesOrchestrator !== null) { throw new NotImplementedException( feature: 'Document::setSignature()->save()/output()/getPdfData()', followUp: 'The high-level PAdES writer seam is not yet wired ... ' . 'Produce a signed PDF via the direct two-phase ' . 'PadesOrchestrator::signDocument() then finalizeSignature() ' . 'buffer API ...', );}Неподписанный PDF, выглядящий подписанным, — именно такой правдоподобный, но ошибочный артефакт, ради предотвращения которого существует этот принцип. Та же позиция проявляется в строгом пути CSS. Незарегистрированное отклонение от спецификации выбрасывает StrictModeViolation в точке обнаружения, а не отрисовывает приблизительный результат, оставляя отклонение незамеченным.
Однократное использование устраняет целый класс догадок. Document одноразовый: строится, отдаётся и отбрасывается. Нет reset() и нет пути повторного использования. Долгоживущий рабочий процесс создаёт свежий экземпляр на каждый запрос через DocumentFactory. Движку никогда не приходится гадать, имеет ли ещё значение остаточное состояние от предыдущего документа, потому что по конструкции такого состояния нет.
Что говорят свидетельства
Заголовок раздела «Что говорят свидетельства»Эта страница Evidence: Code-backed : каждая форма выше процитирована из исходного кода самого движка и его примеров, а не пересказана по намерениям.
- Типизированные сигнатуры с перечислениями — публичный контракт в
PdfDocumentInterface. Стиль вызова с именованными аргументами последовательно используется во всех канонических примерах, которые служат фактическим справочником по API. - Проверка пути до отрисовки с её типизированным
InvalidConfigExceptionи защита «отказ до выдачи» сNotImplementedExceptionпроцитированы дословно из пути вывода фасада документа. - Опорный стандарт — Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 — защита от ошибок пользователя, свойство качества, ради удовлетворения которого в месте вызова существует API, отказывающийся угадывать. Второй опорный стандарт — Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 , и именно поэтому угадывание вокруг подписанного документа никогда не бывает безобидным. Дайджест покрывает объявленный диапазон байтов, исключающий значение подписи, поэтому любая тихая переработка делает его недействительным.
Практический пример
Заголовок раздела «Практический пример»Ниже — небольшая законченная программа. Каждая строка, которая могла бы быть неоднозначной, заявляет своё намерение. Единственный небезопасный вход отклоняется до того, как будет выполнена какая-либо работа.
<?php
declare(strict_types=1);
use NextPDF\Contracts\OutputDestination;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use NextPDF\ValueObjects\PageSize;use NextPDF\Contracts\Orientation;
$document = Document::createStandalone();$document->setTitle('Quarterly Report');
// Intent is explicit: a typed page size and an Orientation enum case,// not a string the engine has to interpret.$document->addPage(PageSize::a4(), Orientation::Landscape);$document->setFont('helvetica', 'B', 16);
// Ambiguous boolean is named, so the call reads as intent.$document->cell(0, 12, 'Quarterly Report', newLine: true);
try { // Unsafe path is rejected before a byte is built. $document->save('php://output/report.pdf');} catch (InvalidConfigException $e) { // "Invalid configuration for key "output_path": expected valid_path, ..." error_log($e->getMessage());
// The String destination is explicit: bytes only, no HTTP headers, // no file side effect. Nothing is inferred from a missing filename. $bytes = $document->output(dest: OutputDestination::String);}Нет пути, на котором эта программа втихую сделала бы что-то не то. Она заявляет намерение и продолжает работу либо называет проблему и останавливается.
Частое заблуждение
Заголовок раздела «Частое заблуждение»Частое возражение — “это просто многословие”. Это не многословие. Это отсутствие скрытых значений по умолчанию. Голый true короче newLine: true ровно на ту меру ясности, которую он убирает. Движок обменивает несколько символов в месте вызова на устранение целой категории ошибок — когда код компилируется, выполняется, создаёт файл и при этом неверен.
Связанное заблуждение в том, что быстрый отказ означает “много выбрасывает”. При обычном использовании NextPDF ничего не выбрасывает. Корректный вход проходит насквозь. Защитные проверки срабатывают только на действительно неоднозначных или небезопасных входах — именно тех, о которых вы хотите узнать немедленно, а не тех, которые хотите угадать.
Пределы и границы
Заголовок раздела «Пределы и границы»Отказ угадывать применяется к намерению и безопасности, а не к любому удобству. У NextPDF по-прежнему есть безопасные значения по умолчанию: книжная ориентация, выравнивание влево, без рамки. Принцип в том, что значение по умолчанию предлагается только там, где оно безопасно и не удивляет, и никогда там, где неверный вывод порождает неверный документ.
Эта страница демонстрирует принцип на поверхности основного публичного API (фасад документа, его контракт и путь вывода). У подсистем есть собственные точки входа, и каждая документирует своё поведение проверки. Приведённые здесь формы актуальны на момент этого обзора. Они иллюстрируют шаблон; они не являются исчерпывающим перечнем каждой защитной проверки в движке.
Описанные защитные проверки с быстрым отказом — это проверки корректности и безопасности. Сами по себе они не являются границей безопасности. Проверка входных данных — только один слой. Философия проектирования и документация по безопасности описывают более широкую позицию.
Сопутствующая документация
Заголовок раздела «Сопутствующая документация»- Философия проектирования NextPDF — принцип, который демонстрирует эта страница, в контексте его приоритетов.
- Ошибки как возможность — что призваны сообщить вам типизированные исключения, которые выбрасывают эти защитные проверки.
- Строгие типы повсюду — как система типов делает «заявляйте своё намерение» обязательным к исполнению, а не рекомендательным.
Глоссарий
Заголовок раздела «Глоссарий»- Подтверждённый кодом (уровень свидетельства) — страница, утверждения которой проверены по исходному коду самого движка или по исполняемому примеру; они процитированы, а не пересказаны.
- Быстрый отказ — отклонение некорректного входа в самой ранней точке с ясной причиной вместо продолжения работы и невнятного отказа позже.
- Именованный аргумент — синтаксис места вызова в PHP (
newLine: true), который связывает значение с параметром по имени, делая иначе неоднозначный литерал самоописательным. - Жизненный цикл однократного использования — одноразовый контракт
Document: создать экземпляр, записать, сохранить, отбросить. Нетreset(), нет повторного использования. Рабочие процессы создают свежий экземпляр на каждый запрос черезDocumentFactory. - PAdES — PDF Advanced Electronic Signatures, семейство профилей ETSI для подписания PDF. Раскрывается при первом использовании; подробно рассматривается на страницах о подписании.