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

Перенос кодовой базы TCPDF 6.x на NextPDF

Пакет nextpdf/compat-legacy предоставляет публичные имена методов, порядок параметров и значения по умолчанию TCPDF 6.x поверх ядра NextPDF через адаптер NextPDF\Compat\Tcpdf\TCPDF. Выполняйте перенос в таком порядке: перейдите на движок с минимальными изменениями, проверьте, что уже работает, включите строгий режим, чтобы найти неработающие места, исправьте места вызова по одному, затем откажитесь от адаптера и перейдите на современный API. Адаптер помогает выполнить перенос, но не является конечной целью.

Предварительные требования на старте:

  • Ядро NextPDF и nextpdf/compat-legacy установлены.
  • У вас есть существующая кодовая база TCPDF 6.x с набором тестов. Набор тестов служит страховкой для каждого из приведённых ниже этапов.

Это практическое руководство. Чтобы узнать, как ведут себя отдельные вызовы TCPDF по методам, прочитайте страницу о покрытии методов. Чтобы изучить полную пофайловую стратегию с кодом, прочитайте основную страницу о переносе. Обе ссылки приведены в разделе “См. также”.

Установите адаптер рядом с ядром. Пока не удаляйте оригинальную библиотеку TCPDF — наличие обеих библиотек позволяет сравнивать вывод по ходу переноса.

Окно терминала
composer require nextpdf/compat-legacy

Прежде чем менять код, убедитесь, что зависимость от движка разрешается (nextpdf/core ^3.0) и набор тестов по-прежнему выполняется.

Адаптер — это слой совместимости, а не форк TCPDF и не побайтово идентичный клон. Из примерно 120 рассмотренных публичных методов TCPDF 6.x около 94 напрямую сопоставляются с операцией NextPDF\Core\Document и ведут себя совместимо для задокументированных параметров. Небольшая часть методов либо принимает устаревшие параметры, которые движок не учитывает (молчаливое игнорирование), либо не даёт никакого вывода (не реализованы или неприменимы). Авторитетная, проверенная тестами матрица покрытия находится в репозитории пакета по пути docs/TCPDF_COVERAGE.md. Если это руководство расходится с матрицей, приоритет имеет матрица.

Весь перенос определяется двумя фактами:

  • Байты вывода различаются. Движок — это независимая реализация PDF 2.0, поэтому байты вывода отличаются от вывода TCPDF, даже когда видимый результат выглядит одинаково. Тесты, которые проверяют точные байты PDF, нужно перенастроить на отрисованное содержимое или структурные свойства.
  • Строгий режим — ваш инструмент аудита. При выключенном строгом режиме (по умолчанию) методы, которые не могут воспроизвести поведение TCPDF, деградируют молча. При включённом строгом режиме такие вызовы выбрасывают TcpdfNotImplementedException, указывая точные проигнорированные параметры и подсказку по переносу. Запускайте строгий режим в отдельном проходе аудита, но никогда в продакшене.

Адаптер также предоставляет обёрнутый документ движка через метод getDocument(), который возвращает NextPDF\Core\Document. Используйте его как путь выхода: переносите места вызова на современный API по одному, пока не сможете удалить адаптер.

ЗадачаПоверхность
Созданиеnew NextPDF\Compat\Tcpdf\TCPDF('P', 'mm', 'A4')
Включаемые глобальные псевдонимыNextPDF\Compat\Tcpdf\LegacyBootstrap::enableAliases()
Включение аудитаTCPDF::setStrictMode(true)
Исключение аудитаNextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException
Запасной выход к современному APITCPDF::getDocument(): NextPDF\Core\Document
ВыводTCPDF::Output(string $name, string $dest)S, F, E, I, D

LegacyBootstrap::enableAliases() идемпотентен. Он регистрирует \TCPDF, \TCPDF_STATIC, \TCPDF_FONTS, \TCPDF_COLORS и \TCPDF_IMAGES только в том случае, если эти классы ещё не существуют. Страницы о покрытии методов и быстром старте, ссылки на которые приведены в разделе “См. также”, описывают полное поведение каждого метода и места назначения вывода.

Измените импорт, оставьте вызовы в стиле TCPDF и создайте PDF.

quickstart-first.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->SetCreator('Quickstart');
$pdf->SetTitle('First Document');
$pdf->SetFont('helvetica', '', 12);
$pdf->AddPage();
$pdf->Cell(0, 10, 'Hello from the NextPDF engine', 1, 1, 'C');
$pdf->Output(__DIR__ . '/quickstart.pdf', 'F');

Output($name, 'F') записывает файл и возвращает пустую строку. В отличие от устаревшего TCPDF, Output() адаптера не выводит данные в активный выходной буфер, поэтому вы можете безопасно вызывать его внутри обработчика очереди или HTTP-обработчика, который сам управляет ответом.

Если вы не можете изменить места вызова, которые создают new \TCPDF(...) в глобальном пространстве имён, включите подключаемые псевдонимы один раз при запуске.

quickstart-alias.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\LegacyBootstrap;
LegacyBootstrap::enableAliases();
// Legacy code now resolves \TCPDF to the adapter:
$pdf = new \TCPDF('P', 'mm', 'A4');
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Legacy call site, modern engine');
$pdf->Output(__DIR__ . '/aliased.pdf', 'F');

Не включайте псевдонимы, пока оригинальная библиотека TCPDF ещё доступна для автозагрузки. Псевдоним пропускается, когда класс \TCPDF уже существует, поэтому вы можете продолжать использовать устаревший TCPDF, сами того не заметив. В ходе переноса отдавайте предпочтение пофайловым импортам.

Безопасный шаг при переносе — аудит в строгом режиме. Запустите репрезентативный продакшен-сценарий или набор тестов с включённым строгим режимом и соберите каждое TcpdfNotImplementedException. Каждое исключение — это отдельная задача: оно называет метод, проигнорированные параметры и подсказку.

migration-audit.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;
use NextPDF\Compat\Tcpdf\TCPDF;
function renderInvoice(TCPDF $pdf): void
{
// ... your existing rendering code, unchanged ...
}
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->setStrictMode(true);
try {
renderInvoice($pdf);
$pdf->Output(__DIR__ . '/audit.pdf', 'F');
} catch (TcpdfNotImplementedException $exception) {
// Each message names the method, the ignored parameters, and a hint.
fwrite(STDERR, 'MIGRATION GAP: ' . $exception->getMessage() . "\n");
}

Для каждого расхождения выберите самое простое корректное исправление: уберите параметр, на который вы никогда не полагались, или выразите намерение через современный API с помощью getDocument(). Запасной выход покрывает всё, что нельзя выразить через поверхность TCPDF.

migration-escape-hatch.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();
$pdf->AddPage();
// Legacy path stays for the parts that already work:
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Header line', 0, 1);
// Modern path for what the TCPDF surface cannot express here —
// for example a clickable image (the legacy Image() link parameter
// is one of the silently ignored parameters):
$document = $pdf->getDocument();
$document->image('logo.png', 10, 30, 40, 0);
$document->link(10, 30, 40, 20, 'https://example.com');

Запускайте строгий режим как отдельную задачу непрерывной интеграции (CI), затем выключайте его и развёртывайте проверенный путь выполнения кода. Сохраняйте периодическую задачу CI в строгом режиме, чтобы выявлять регрессии по мере рефакторинга.

  • MultiCell() возвращает 1, Write() возвращает 0. Это заполнители для совместимости, а не вычисленные значения. Скорректируйте любой код, который ветвится по этим возвращаемым значениям.
  • Error() выбрасывает исключение вместо вызова die(). Адаптер вызывает RuntimeException. Код, который полагается на завершение процесса, должен перехватывать это исключение.
  • Молчаливо игнорируемые параметры. Такие методы, как Image(), writeHTML(), SetProtection() и Bookmark(), принимают устаревшие параметры, которые игнорируются. Используйте строгий режим, чтобы найти их. Для кликабельного изображения сначала выведите изображение, затем добавьте Document::link() поверх того же прямоугольника.
  • Нереализованные методы. setSignature(), addEmptySignatureAppearance() и endPage() — это пустые операции, которые выбрасывают исключение в строгом режиме; Open() — это безопасная пустая операция, которая никогда не выбрасывает исключение. Удалите endPage() и Open(). Для подписания нужна коммерческая редакция NextPDF и современный API подписи.
  • Версия PDF фиксирована. setPDFVersion() не может задать более старую версию PDF как целевую; вывод всегда соответствует PDF 2.0. setUserRights() устарел в PDF 2.0 и игнорируется с уведомлением.
  • Конфликт псевдонимов. Если что-либо после удаления tecnickcom/tcpdf по-прежнему разрешается в настоящий класс TCPDF, сработала оговорка о псевдонимах — явно импортируйте адаптер в этих местах вызова.

Адаптер делегирует работу движку; стоимость построения документа масштабируется в зависимости от содержимого, а не от слоя адаптера. Поскольку Output() адаптера не пишет в выходной буфер, его безопасно вызывать внутри обработчика очереди — выносите тяжёлую генерацию в стиле TCPDF из потока запроса так же, как вынесли бы любую генерацию NextPDF. Перенастройка тестов байтового уровня на отрисованное содержимое — это разовая работа, которая даёт вам тесты, устойчивые к будущим обновлениям движка.

  • Шифрование. SetProtection() игнорирует устаревшие параметры mode и pubkeys; для стандартного обработчика движок использует AES-256. Для шифрования на основе сертификатов используйте современную точку входа шифрования с открытым ключом, которую предоставляет адаптер, а не устаревшие параметры.
  • Подписание ограничено. Поддержка базовых подписей — это возможность коммерческой редакции, доступная через современный API подписи с объектом-значением сертификата; устаревший setSignature() — это пустая операция. Это руководство не делает заявлений о профилях подписей с долгосрочной проверкой или с метками времени ни для какой редакции.
  • Завершайте работу с явной ошибкой во время аудита. Строгий режим делает молчаливую потерю параметров видимой, поэтому вы видите, когда адаптер не учёл намерение вызывающего кода. Рассматривайте собранные исключения как список задач по переносу, а не как поведение в продакшене.
  • Никогда не пишите пустой блок catch. Пример аудита перехватывает TcpdfNotImplementedException и записывает конкретную строку с задачей.

Полное описание подхода к шифрованию и подписям при переносе приведено на странице compat-legacy о безопасности и эксплуатации.

Это руководство не делает собственных нормативных заявлений о соответствии стандартам. Адаптер записывает вывод в формате PDF 2.0 (ISO 32000-2) и не может выбрать более старую версию как целевую. Это поведение и соответствующий пункт зафиксированы на основной странице о покрытии методов; там же отражены принцип OWASP о явном завершении с ошибкой, лежащий в основе строгого режима, и трактовка аудита покрытия с точки зрения функциональной полноты по ISO/IEC 25023. Эта страница с рецептом повторно излагает порядок использования и отсылает по этим вопросам к основной странице.