Возврат сгенерированного PDF из контроллера
Краткий обзор
Заголовок раздела «Краткий обзор»Сгенерируйте PDF-файл в действии контроллера и верните его как HTTP-ответ. Каждая интеграция с фреймворком включает помощник PdfResponse: он создаёт объект ответа, задаёт Content-Type: application/pdf, добавляет заголовки безопасности и очищает имя файла. В этом руководстве рассматриваются три режима доставки для Laravel, Symfony и CodeIgniter 4: встроенный предварительный просмотр, загрузка файла и потоковая передача.
Сначала проверьте предварительные требования, чтобы маршрут контроллера был готов к работе:
- Ядро NextPDF установлено.
- Установлена одна интеграция с фреймворком, и её поставщик служб, бандл или служба обнаружены. Перед началом работы проверьте это на странице установки для вашего фреймворка.
- Потоковый режим не требует дополнительных пакетов. Каждая интеграция включает потоковый вариант наряду с буферизованным вариантом.
Это практическое руководство. Предполагается, что вы уже знаете, как направить запрос к контроллеру в своём фреймворке. Чтобы получить первый рабочий пример для каждого фреймворка, см. соответствующее краткое руководство в разделе “См. также”.
Установка
Заголовок раздела «Установка»Установите интеграцию для своего фреймворка. Выполните одну из команд ниже.
composer require nextpdf/laravelcomposer require nextpdf/symfonycomposer require nextpdf/codeigniterДля Laravel опубликуйте конфигурацию после установки.
php artisan vendor:publish --tag=nextpdf-configSymfony регистрирует бандл через Flex, а CodeIgniter обнаруживает службу автоматически. Прежде чем продолжить, убедитесь на странице установки вашего фреймворка, что обнаружение прошло успешно.
Концептуальный обзор
Заголовок раздела «Концептуальный обзор»Каждая интеграция с фреймворком следует одной и той же трёхчастной схеме: вы получаете новый документ, записываете в него содержимое и передаёте документ фабрике PdfResponse, которая возвращает HTTP-ответ. API документа (addPage(), cell(), setFont()) относится к основному движку и одинаков во всех фреймворках. Фабрики ответов различаются только возвращаемым классом ответа, потому что у каждого фреймворка свой тип HTTP-ответа.
PdfResponse предлагает три режима доставки. Встроенный режим задаёт заголовок Content-Disposition: inline, поэтому браузер показывает PDF во вкладке просмотра. Загрузка задаёт Content-Disposition: attachment, поэтому браузер сохраняет файл. Потоковая передача выдаёт тело PDF фиксированными порциями вместо буферизации всего документа в памяти. Выбирайте её для больших документов, когда пиковое потребление памяти важнее известного значения Content-Length.
Получите документ через обычный путь разрешения вашего фреймворка:
- Laravel — разрешите
NextPDF\Contracts\DocumentFactoryInterfaceиз контейнера с помощьюapp(...)и вызовитеcreate(), который возвращает новыйNextPDF\Core\Document— конкретный тип, принимаемый фабрикамиPdfResponse. - Symfony — внедрите
NextPDF\Symfony\Service\PdfFactoryи вызовитеcreate(), который возвращает новыйNextPDF\Core\Documentс уже применёнными настройками документа по умолчанию. - CodeIgniter 4 — разрешите библиотеку
PdfчерезServices::pdf()(или помощникpdf()) либо получите чистый документ черезpdf_document().
Поверхность API
Заголовок раздела «Поверхность API»| Задача | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Новый документ | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() | pdf_document() / Services::pdf()->document() |
| Встроенный ответ | PdfResponse::inline($doc, $name) | PdfResponse::inline($doc, $name) | $pdf->inline($name) / PdfResponse::inline($doc, $name) |
| Ответ для загрузки | PdfResponse::download($doc, $name) | PdfResponse::download($doc, $name) | $pdf->download($name) / PdfResponse::download($doc, $name) |
| Потоковый встроенный | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Потоковая загрузка | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Возвращаемый тип | Illuminate\Http\Response (потоковый: StreamedResponse) | Symfony\Component\HttpFoundation\Response (потоковый: StreamedResponse) | CodeIgniter\HTTP\DownloadResponse |
В Laravel PdfResponse находится по адресу NextPDF\Laravel\Http\PdfResponse, в Symfony — NextPDF\Symfony\Http\PdfResponse, а в CodeIgniter — NextPDF\CodeIgniter\Http\PdfResponse. Страница безопасности и эксплуатации каждой интеграции описывает полное поведение ответа в этом пакете: набор заголовков, правила disposition и очистку имени файла. Ссылки на эти страницы приведены в разделе “См. также”.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»Ниже приведено минимальное действие загрузки для каждого фреймворка. Вызовы документа используют один и тот же API ядра. Меняется только структура контроллера.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;
final class ReportController extends Controller{ public function download(): Response { $document = app(DocumentFactoryInterface::class)->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Attribute\Route;
final class ReportController{ #[Route('/report', name: 'report_pdf')] public function download(PdfFactory $pdf): Response { $document = $pdf->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;use NextPDF\CodeIgniter\Config\Services;
final class ReportController extends BaseController{ public function download(): DownloadResponse { $pdf = Services::pdf(); $pdf->document()->addPage(); $pdf->document()->cell(0, 10, 'Monthly report');
return $pdf->download('report.pdf'); }}Чтобы открыть предварительный просмотр в браузере вместо загрузки, замените вызов download(...) на inline(...) в Laravel и Symfony либо на $pdf->inline('report.pdf') в CodeIgniter. Значение disposition меняется на inline, а все остальные заголовки остаются прежними.
Пример кода — продакшен
Заголовок раздела «Пример кода — продакшен»Действие для продакшена внедряет свои зависимости, перехватывает наиболее конкретное исключение, задокументированное интеграцией, записывает класс сбоя в журнал без утечки трассировки и возвращает явную ошибку HTTP. В примере ниже используется внедрение через конструктор Laravel. Эквиваленты для Symfony и CodeIgniter следуют той же схеме и приведены на странице эксплуатации в продакшене каждой интеграции.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly DocumentFactoryInterface $documents, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
return PdfResponse::download( $document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Log the exception class, never the message or a stack trace, // so internal detail does not leak into the log sink. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}Внедрите DocumentFactoryInterface и вызывайте create() в каждом действии. Он возвращает новый NextPDF\Core\Document — конкретный тип, который принимают фабрики PdfResponse для Laravel. Разрешение нового документа для каждого запроса сохраняет возможность подмены фабрики в тестах. Не используйте повторно один экземпляр контроллера для двух логически не связанных документов в рамках одного длительно работающего рабочего процесса.
Для очень больших документов замените буферизованную фабрику потоковой, чтобы ограничить пиковое потребление памяти. Потоковый вариант возвращает StreamedResponse (Laravel и Symfony) и выдаёт тело фиксированными порциями. Он намеренно не указывает Content-Length, поэтому индикаторы хода загрузки и прокси, зависящие от длины тела, не видят известного размера. Для небольших ответов, чувствительных к задержке, предпочитайте буферизованные download() / inline().
$document = $this->documents->create();// ... emit content onto $document ...return PdfResponse::streamDownload($document, 'annual-report.pdf');Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- Новый документ на каждый вызов. Во всех трёх интеграциях документ создаётся фабрикой заново при каждом разрешении. Не кэшируйте разрешённый документ между логическими документами или между запросами в длительно работающем рабочем процессе. Иначе старое состояние содержимого перейдёт дальше.
- Пустое имя файла. Пустое имя файла, переданное фабрике
PdfResponse, заменяется именем по умолчанию (document.pdf), а не создаёт пустое значение disposition. Передавайте явное, осмысленное имя файла. - Имена файлов вне ASCII. Ответ Laravel автоматически добавляет параметр RFC 5987
filename*=для имён вне ASCII, а для имён ASCII используется обычный параметр. Не кодируйте имя файла вручную. - Потоковые ответы за буферизующим прокси. Прокси, который буферизует всё тело, сводит на нет выигрыш потоковой передачи по памяти. Настройте прокси на потоковую передачу PDF-ответов либо используйте на этом маршруте буферизованный ответ.
- Потоковый обратный вызов Symfony. Потоковый вариант Symfony возвращает
StreamedResponse, обратный вызов которого сбрасывает вывод. Не записывайте в тело ответа самостоятельно после того, как вы его вернули.
Производительность
Заголовок раздела «Производительность»Синхронная генерация внутри контроллера блокирует запрос на всё время сборки PDF. Одностраничный документ обычно укладывается в типичный бюджет запроса. Для многостраничного или пакетного вывода вынесите генерацию из потока запроса в задание очереди — см. “Генерация PDF в задании очереди”. Потоковые варианты снижают пиковое потребление памяти для больших документов ценой неизвестного Content-Length. Выбирайте их, когда ограничением является память, а индикатор прогресса не требуется.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»- Фабрики
PdfResponseприменяют фиксированный набор заголовков, усиливающих безопасность ответа, и очищают имя файла загрузки в каждой интеграции. Не добавляйте эти заголовки самостоятельно. - Никогда не подставляйте непроверенный пользовательский ввод напрямую в имя файла, которое вы передаёте фабрике. Передавайте значение, которое вы контролируете, и позвольте фабрике очистить его как второй уровень защиты.
- В блоке catch регистрируйте класс исключения и идентификатор корреляции, а не сообщение исключения или трассировку. Необработанная трассировка в приёмнике журналов — это утечка информации.
- Никогда не пишите пустой блок
catch. Каждый пример здесь регистрирует и возвращает определённый ответ об ошибке.
Страница безопасности и эксплуатации каждой интеграции описывает её модель угроз: набор заголовков, правила очистки имени файла и время жизни привязки документа.
Соответствие
Заголовок раздела «Соответствие»Это руководство не делает нормативных заявлений о соответствии стандартам. Каждый показанный вызов API — это проверенная публичная поверхность указанной интеграции, сверенная со страницами быстрого старта и эксплуатации в продакшене каждого пакета. Страницы эксплуатации в продакшене, ссылки на которые приведены в разделе “См. также”, описывают семантику заголовков и поведение привязки в контейнере, на которые опираются интеграции, вместе с соответствующими ссылками на PSR. Эта страница руководства повторяет порядок использования, а нормативные ссылки оставляет этим страницам.
См. также
Заголовок раздела «См. также»- Генерация PDF в задании очереди — вынесите эту работу из потока запроса.
- Эксплуатация Laravel в продакшене — контроллер с внедрением зависимостей, набор заголовков и контракт привязки PSR-11.
- Быстрый старт Symfony — контроллер, встроенный режим, потоковая передача и модель ответа.
- Быстрый старт CodeIgniter —
Services::pdf(), помощникpdf()иPdfResponse. - Выбор интеграции — выберите подходящий пакет для фреймворка.