Перейти к содержимому

Конвейерная модель

Spec: ISO 32000-2, §7.5 Evidence: Code-backed

Документ NextPDF не создаётся за один непрозрачный шаг. Он проходит через небольшое число явных этапов: фасад фиксирует намерение, слой содержимого превращает его в модель, а писатель сериализует эту модель в соответствующий стандарту PDF. На этой странице описана эта структура и объясняется, почему она подходит движку.

Файл PDF сам по себе имеет многослойную структуру — заголовок, тело объектов, таблицу перекрёстных ссылок и трейлер, — и писатель должен собрать всё это согласованно. Если движок, который строит такой файл, устроен как одна запутанная процедура, каждое изменение ставит под угрозу любой результат. Тогда единственный способ получить уверенность — полностью отрисовать документы и проверить их визуально, а это медленно, запоздало и неубедительно.

Явный конвейер меняет эту ситуацию. У каждого этапа есть одна задача и типизированная граница, поэтому вы можете анализировать изменение и тестировать его на том этапе, которого оно касается, а не только в самом конце файла. Эта архитектура — прежде всего выбор в пользу тестируемости и расширяемости.

  • Публичная точка входа — это фасад Document: текучий, одноразовый, безопасный для воркеров построитель, который фиксирует, что вам нужно, а не как это сериализуется.
  • Фасад делегирует работу примерно двум десяткам узкоспециализированных трейтов-обязанностей (вывод текста, рисование, страницы, безопасность, навигация и так далее): по одной обязанности на каждый трейт, вместо одного гигантского класса.
  • Содержимое поступает одним из двух путей: прямое рисование (графические примитивы) или движок HTML/CSS. Оба создают одну и ту же внутреннюю модель документа.
  • Выделенный писатель PDF сериализует эту модель, выбирая стратегию PDF 1.4 / 1.7 / 2.0. Создание корректной структуры файла происходит здесь и больше нигде.
  • Долгоживущее состояние (реестры шрифтов и изображений) привязано к процессу и является общим; состояние отдельного запроса (документ) создаётся заново и никогда не используется повторно. Эта граница явная, что и делает среды выполнения воркеров безопасными.

Проще всего увидеть эту модель, проследив путь документа от вызова до байтов.

  1. Document facade Fluent, use-once builder; records intent via concern traits.
  2. Content production Direct drawing or the HTML/CSS engine — both build one document model.
  3. Document model Accumulated pages, content, and resources held as typed state.
  4. PDF writer Serialises the model; selects a PDF 1.4 / 1.7 / 2.0 strategy.
  5. Conforming PDF Header, object body, cross-reference table, trailer.
Путь документа через NextPDF: у каждого этапа одна обязанность и типизированная граница, поэтому о нём можно рассуждать и тестировать его изолированно.

Два архитектурных решения делают это чем-то большим, чем просто схема.

Фасад собран из частей, а не монолитен. Document не реализует каждую возможность сам; он делегирует каждую область выделенному трейту-обязанности — вывод текста, рисование, страницы, безопасность, типографика, навигация, транзакции и так далее. Место нового метода документа — в трейте, владеющем соответствующей областью, а не в самом фасаде. Класс, который вы вызываете, остаётся небольшим, а обязанности — разделёнными.

Писатель единолично владеет структурой файла. Формирование содержимого решает, какие метки и объекты существуют; писатель решает, как они становятся корректным файлом PDF, в том числе какая стратегия версии применяется. Это разделение закреплено как архитектурное правило: код макета и содержимого не порождает итоговую структуру файла, а писатель не принимает решений о макете. Преимущество в том, что вопрос «является ли результат корректным PDF?» проверяется ровно в одном месте.

Граница времени жизни — часть модели, а не позднее дополнение. Реестры шрифтов и изображений живут в течение всего срока жизни процесса и являются общими для всех запросов; документ, его контекст отрисовки и писатель создаются для каждого запроса и затем уничтожаются. В среде выполнения воркера это различие отделяет безопасное повторное использование от порчи данных между запросами. Именно поэтому оно закреплено в архитектуре, а не оставлено на дисциплину.

Эта страница имеет статус Evidence: Code-backed . Эти этапы отражают реальную структуру основного репозитория:

  • Фасад и его делегирование — это src/Core/Document.php плюс трейты-обязанности в src/Core/Concerns/ (вывод текста, вывод, рисование, страницы, безопасность, типографика, навигация, транзакции и другие — каждый с единственной обязанностью).
  • Два пути содержимого — это движок HTML/CSS (src/Html/) и прямое рисование (src/Graphics/), и оба наполняют внутреннюю модель.
  • Сериализация и стратегия версии PDF находятся в src/Writer/ (PdfWriter.php, с явными классами стратегий PDF 1.4 / 1.7 / 2.0).
  • Граница между временем жизни процесса и временем жизни запроса — это безопасный для воркеров подход, зафиксированный в обзоре архитектуры и показанный в поставляемом примере фабрики воркеров: он использует общие FontRegistry и ImageRegistry для разных запросов, но каждый Document создаёт заново.

Конечный результат задан форматом. Результат работы писателя должен включать заголовок, тело объектов, таблицу перекрёстных ссылок и трейлер согласно Spec: ISO 32000-2, §7.5 . Когда эта обязанность сосредоточена на одном этапе, остальная часть движка может заниматься содержимым, а не сборкой структуры файла.

Задача фасада — сделать так, чтобы намерение выражалось как намерение. Путь содержимого и писатель остаются невидимыми в месте вызова:

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone(); // facade
$doc->setTitle('Quarterly Report'); // metadata concern
$doc->addPage(); // pages concern
$doc->setFont('helvetica', 'B', 16); // typography concern
$doc->cell(0, 12, 'Summary', newLine: true); // text-output concern
$doc->writeHtml('<p>Generated in-process.</p>'); // HTML content path
$doc->save(__DIR__ . '/report.pdf'); // writer stage

Каждый вызов попадает в свою отдельную обязанность. Два разных пути содержимого наполняют одну и ту же модель. Ровно один этап — save() — превращает модель в байты файла. Ничто в месте вызова не должно знать, как строится таблица перекрёстных ссылок.

Частое неверное толкование состоит в том, что «конвейер» подразумевает потоковый push-API, который вы соединяете этап за этапом, как канал Unix. Это не так. Конвейер здесь — это архитектурная декомпозиция: этапы с единственными обязанностями и типизированными границами. Вы по-прежнему работаете через текучий фасад. Этапы — это то, как движок построен и протестирован, а не транспорт, который вы собираете вручную.

Связанная ошибка — считать, что фасад и есть движок. Это точка входа. Основная работа распределена между трейтами-обязанностями, двумя путями содержимого и писателем. Именно это распределение и означает, что изменение одной возможности не ставит под угрозу любой результат.

На этой странице описывается структура конвейера, а не внутренний API какого-либо отдельного этапа. Точный перечень трейтов-обязанностей, правила выбора стратегии писателя и поля модели содержимого определяются кодом и справочником, а не этим описанием. Число трейтов — это деталь реализации, которая может меняться без изменения модели. На этой странице не рассматриваются внутренние этапы движка HTML (отдельная тема) и поведение писателя в части потоковой обработки и памяти (тоже отдельная тема). Структурные утверждения верны на дату проверки этой страницы; авторитетным источником являются src/Core/, src/Html/, src/Graphics/ и src/Writer/ основного репозитория.

Конвейерная модель одинакова во всех редакциях; редакции добавляют возможности внутри этапов, а не новые этапы:

Pipeline model (Конвейерная модель) — edition availability
Edition Availability
Core Core реализует полный конвейер фасад → содержимое → писатель.
Pro Pro добавляет возможности внутри существующих этапов, а не новые этапы.
Enterprise Enterprise добавляет возможности внутри существующих этапов, а не новые этапы.
  • Фасад — публичная точка входа Document: текучий, одноразовый построитель, который фиксирует намерение и делегирует работу трейтам-обязанностям.
  • Трейт-обязанность — узкоспециализированный PHP-трейт, составляющий фасад; каждый владеет одной функциональной областью (вывод текста, рисование, страницы, безопасность и так далее).
  • Путь содержимого — один из двух способов, которыми содержимое попадает в модель: прямое рисование или движок HTML/CSS.
  • Модель документа — внутреннее типизированное накопление страниц, содержимого и ресурсов в движке до сериализации.
  • Этап писателя — компонент, который сериализует модель в корректный PDF, выбирая стратегию PDF 1.4 / 1.7 / 2.0.
  • Безопасный для воркеров — спроектированный так, что состояние с временем жизни процесса используется совместно безопасно, тогда как состояние отдельного запроса создаётся заново и никогда не используется повторно.