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

Использование в продакшене с CodeIgniter 4

В продакшене контроллеры получают конкретные сервисы NextPDF, явно обрабатывают документированную иерархию исключений и отправляют сигналы наблюдаемости. Долгие операции с Portable Document Format (PDF) выносите из запроса в очередь CodeIgniter 4 Queue.

CodeIgniter 4 получает сервисы пакета через собственный локатор. В паттерне service locator объект получает контейнер и сам извлекает из него свои зависимости. PHP Standard Recommendation (PSR) не рекомендует этот паттерн (PSR-11 §1.3, модальность SHOULD NOT). Чтобы следовать этой рекомендации, получайте каждый сервис NextPDF один раз на границе контроллера, а дальше передавайте внутрь конкретный объект. Не передавайте класс Services или контейнер в код вашей предметной области.

В каждом примере на PHP declare(strict_types=1); вынесено на отдельную строку (PSR-12 §x1.x3.p34).

Задача продакшенаПроверенная поверхность
Разрешение сервисовServices::pdf(false), Services::pdfDocument(false), Services::documentFactory()
Построение ответаPdfResponse::download() / inline()DownloadResponse
Перехват сбоевNextPDF\Exception\NextPdfException (базовый тип экосистемы)
Асинхронная генерацияGeneratePdfJob, зарегистрированный в Config\Queue::$jobHandlers
Проверки пути и вызываемого объектаGeneratePdfJob выбрасывает InvalidArgumentException

Контроллер для продакшена — обработка ошибок и наблюдаемость

Заголовок раздела «Контроллер для продакшена — обработка ошибок и наблюдаемость»

Основной движок выбрасывает исключения, которые все расширяют NextPDF\Exception\NextPdfException. Перехватывайте этот единственный тип, чтобы охватить сбои ядра и расширений. Блок catch ниже записывает контекст в журнал и возвращает явный ответ об ошибке; пустого catch здесь нет.

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\ResponseInterface;
use NextPDF\CodeIgniter\Config\Services;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final class InvoiceController extends BaseController
{
public function download(int $id): DownloadResponse|ResponseInterface
{
/** @var LoggerInterface $logger */
$logger = \service('logger');
$start = \hrtime(true);
try {
$pdf = Services::pdf(false);
$pdf->document()->addPage();
$pdf->document()->cell(0, 10, "Invoice #{$id}");
$response = $pdf->download("invoice-{$id}.pdf");
$logger->info('pdf.invoice.generated', [
'invoice_id' => $id,
'elapsed_ms' => (\hrtime(true) - $start) / 1_000_000,
]);
return $response;
} catch (NextPdfException $e) {
$logger->error('pdf.invoice.failed', [
'invoice_id' => $id,
'exception' => $e::class,
'message' => $e->getMessage(),
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_INTERNAL_SERVER_ERROR)
->setJSON(['error' => 'pdf_generation_failed', 'invoice_id' => $id]);
}
}
}

Services::pdf(false) при каждом вызове возвращает новый экземпляр библиотеки и новый базовый документ. Параллельные запросы никогда не используют общее состояние документа. Функциональные тесты пакета проверяют это поведение.

Безопасные для воркеров жизненные циклы сервисов

Заголовок раздела «Безопасные для воркеров жизненные циклы сервисов»

По своей конструкции реестры шрифтов и изображений являются синглтонами на время жизни процесса. Реестр шрифтов прогревается и блокируется один раз. Реестр изображений — это ограниченный кеш с вытеснением давно неиспользуемых элементов (LRU). В долгоживущем воркере (сервер CodeIgniter spark, runner в стиле RoadRunner или воркер очереди) это сделано намеренно: затратные реестры сохраняются, тогда как каждый документ создаётся заново. Не запрашивайте общий документ (Services::pdfDocument(true)) в коде запроса или задачи: он существует только для сброса в тестах и иначе приводил бы к совместному использованию содержимого между запросами.

GeneratePdfJob выполняет генерацию PDF вне запроса через codeigniter4/queue. Для среды выполнения очереди нужны две настройки. Настройте обе правильно.

1. Зарегистрируйте обработчик задачи по имени

Заголовок раздела «1. Зарегистрируйте обработчик задачи по имени»

Очередь разрешает задачу по ключу-имени, а не по строке класса. Обработчик очереди сверяет имя поставленной в очередь задачи с ключами Config\Queue::$jobHandlers. Неизвестное имя отклоняется с CodeIgniter\Queue\Exceptions\QueueException. Зарегистрируйте задачу в app/Config/Queue.php:

<?php
declare(strict_types=1);
namespace Config;
use CodeIgniter\Queue\Config\Queue as BaseQueue;
use NextPDF\CodeIgniter\Jobs\GeneratePdfJob;
final class Queue extends BaseQueue
{
/** @var array<string, class-string> */
public array $jobHandlers = [
'generate-pdf' => GeneratePdfJob::class,
];
}

2. Отправляйте задачу по зарегистрированному имени

Заголовок раздела «2. Отправляйте задачу по зарегистрированному имени»

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

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
final class InvoiceController extends BaseController
{
public function queueInvoice(int $id): ResponseInterface
{
\service('queue')->push('pdf-queue', 'generate-pdf', [
'builder' => 'App\\PdfBuilders\\InvoiceBuilder::build',
'outputPath' => WRITEPATH . 'pdfs/invoice-' . $id . '.pdf',
'context' => ['invoice_id' => $id],
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_ACCEPTED)
->setJSON(['status' => 'queued', 'invoice_id' => $id]);
}
}

Задача допускает вызываемые билдеры только из пространства имён App\PdfBuilders и ограничивает пути вывода каталогом WRITEPATH/pdfs/. Реализуйте билдер как статический метод. Он получает новый Document и массив контекста, а затем возвращает документ.

<?php
declare(strict_types=1);
namespace App\PdfBuilders;
use NextPDF\Core\Document;
final class InvoiceBuilder
{
/** @param array<string, mixed> $context */
public static function build(Document $document, array $context): Document
{
$invoiceId = (int) ($context['invoice_id'] ?? 0);
$document->addPage();
$document->cell(0, 10, "Invoice #{$invoiceId}");
return $document;
}
}
Окно терминала
php spark queue:work pdf-queue

При каждом запуске задача начинает с нового документа из Services::pdfDocument(). Затем она применяет билдер и сохраняет файл по проверенному пути. Тесты пакета проверяют, что два последовательных запуска задачи не используют общее состояние документа.

  • Очередь отклоняет GeneratePdfJob::class в качестве имени задачи при постановке в очередь, потому что это не зарегистрированный ключ 'generate-pdf'. Всегда помещайте в очередь ключ из jobHandlers.
  • Строка билдера должна точно соответствовать App\PdfBuilders\<Class>::<method>. Функции, другие пространства имён, а также полезные данные с префиксом или суффиксом вызывают InvalidArgumentException ещё до выполнения какого-либо кода.
  • Путь вывода должен разрешаться внутри WRITEPATH/pdfs/ и заканчиваться на .pdf (без учёта регистра). Пути с обходом каталогов и с префиксом соседнего каталога отклоняются.
  • codeigniter4/queue — это зависимость пакета только для разработки. Подключайте её в приложении, которое запускает воркеры.

Реестры создаются один раз для каждого процесса воркера. Затраты на построение документа растут вместе с содержимым, а не с адаптером. Для крупных пакетных задач используйте путь через очередь, чтобы воркеры запросов оставались отзывчивыми. Задавайте performance_budget в любом рецепте с измеримой целью.

Задача очереди — поверхность с наивысшим риском. Когда брокер доступен, злоумышленник может влиять на полезные данные очереди. Список разрешённых вызываемых объектов и ограничение путей описаны в /integrations/codeigniter/security-and-operations/ вместе с проверенными случаями отклонения.

  • Контроллеры получают конкретные сервисы, а не контейнер, что согласуется с рекомендацией PSR-11 §1.3 по паттерну service locator.

Ядро NextPDF распространяется под Apache-2.0. Чтобы создавать подписанный вывод и вывод PDF/A в задачах очереди, установите NextPDF Pro или Enterprise в окружении воркера. Пакет CodeIgniter предоставляет соответствующие методы сервиса. Они возвращают null, пока не установлен соответствующий пакет Premium. См. </get-license/?intent=codeigniter-async-signing>.

  • /integrations/codeigniter/quickstart/ — минимальный вариант этих контроллеров.
  • /integrations/codeigniter/configuration/ — подписание, служба меток времени (TSA) и настройка путей.
  • /integrations/codeigniter/security-and-operations/ — модель угроз очереди и усиление защиты.
  • /integrations/codeigniter/troubleshooting/ — режимы сбоев очереди и обнаружения.
  • /integrations/codeigniter/integration/ — справочник по подключению и дымовой тест.