NextPDF Symfony 快速上手
注入 PdfFactory,构建一份 Document,再通过 PdfResponse 将其返回。如需在后台生成 PDF,可将一条 GeneratePdfMessage 分派到 Messenger 传输层。
步骤 1 — 在控制器中生成 PDF
标题为“步骤 1 — 在控制器中生成 PDF”的章节注入 NextPDF\Symfony\Service\PdfFactory。它的 create() 方法会返回一份全新的 NextPDF\Core\Document。系统会自动应用已配置的默认值:创建者、作者和语言。最后通过 NextPDF\Symfony\Http\PdfResponse 返回这份文档。
<?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 InvoiceController{ #[Route('/invoice/{number}', name: 'invoice_pdf')] public function download(PdfFactory $pdf, string $number): Response { $doc = $pdf->create(); $doc->addPage(); $doc->cell(0, 10, "Invoice #{$number}", newLine: true); $doc->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download($doc, "invoice-{$number}.pdf"); }}PdfResponse::download() 会返回一个 Symfony\Component\HttpFoundation\Response。响应会包含 Content-Type: application/pdf、attachment 处置方式、一个 Content-Length,以及 bundle 固定设置的安全响应头。Symfony 官方文档说明了标准 Response 类及其响应头模型(https://symfony.com/doc/current/components/http_foundation.html)。
步骤 2 — 以内嵌方式显示 PDF
标题为“步骤 2 — 以内嵌方式显示 PDF”的章节如需让浏览器直接显示 PDF,而不是下载文件,请使用 inline():
return PdfResponse::inline($doc, 'preview.pdf');处置方式会变为 inline。其余响应头保持不变。
步骤 3 — 流式传输大型 PDF
标题为“步骤 3 — 流式传输大型 PDF”的章节对于大型文档,流式版本会以 64 KB 为单位分块输出 PDF,从而降低峰值内存用量。它们会返回一个 Symfony\Component\HttpFoundation\StreamedResponse,并省略 Content-Length。
return PdfResponse::streamDownload($doc, 'annual-report.pdf');streamInline() 则是内嵌显示的对应版本。Symfony 官方文档说明了 StreamedResponse 的回调契约,即一个负责刷新输出的 void callable(https://symfony.com/doc/current/components/http_foundation.html)。
步骤 4 — 以异步方式生成 PDF
标题为“步骤 4 — 以异步方式生成 PDF”的章节安装 symfony/messenger 后,你就可以把生成工作移出请求线程。
4a — 实现 builder
标题为“4a — 实现 builder”的章节实现 NextPDF\Symfony\Message\PdfBuilderInterface。handler 会传入一份全新且已预先配置好的 Document,同时也会传入消息中可序列化的 context。
<?php
declare(strict_types=1);
namespace App\Pdf;
use NextPDF\Core\Document;use NextPDF\Symfony\Message\PdfBuilderInterface;
final class InvoicePdfBuilder implements PdfBuilderInterface{ public function build(Document $document, array $context): Document { $document->addPage(); $document->setFont('dejavusans', '', 12); $document->cell(0, 10, 'Invoice #' . $context['invoice_id']);
return $document; }}4b — 在 locator 中注册 builder
标题为“4b — 在 locator 中注册 builder”的章节handler 会通过 resolve(解析)方式,从一个以类名为键的 PSR-11 服务 locator 中取出 builder。正因如此,只有已注册的 builder 才能被取用。请在 config/services.yaml 中把 builder 加入一个 locator:
services: App\Pdf\InvoicePdfBuilder: ~
nextpdf.pdf_builder_locator: class: Symfony\Component\DependencyInjection\ServiceLocator arguments: - 'App\Pdf\InvoicePdfBuilder': '@App\Pdf\InvoicePdfBuilder' tags: ['container.service_locator']
NextPDF\Symfony\Message\GeneratePdfHandler: arguments: $builderLocator: '@nextpdf.pdf_builder_locator'handler 会使用 builder 的 class-string id 向 locator 索取它。PSR-11 容器标识符是一个能唯一标识某个条目的字符串(PSR-11 §1.1.2)。
4c — 分派消息
标题为“4c — 分派消息”的章节注入 Symfony\Component\Messenger\MessageBusInterface,然后分派消息:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Pdf\InvoicePdfBuilder;use NextPDF\Symfony\Message\GeneratePdfMessage;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Messenger\MessageBusInterface;use Symfony\Component\Routing\Attribute\Route;
final class ReportController{ #[Route('/invoice/{id}/queue', name: 'invoice_queue')] public function queue(MessageBusInterface $bus, int $id): Response { $bus->dispatch(new GeneratePdfMessage( builderClass: InvoicePdfBuilder::class, outputPath: '/var/storage/invoices/' . $id . '.pdf', builderContext: ['invoice_id' => $id], ));
return new Response('PDF generation queued.', 202); }}GeneratePdfMessage 是一个 readonly DTO。它的构造函数会拒绝空字符串或非 .pdf 的输出路径、路径穿越片段、流包装器协议,以及 null 字节。同时,它还要求 builderClass 必须是语法上合法的类名。handler 会在执行阶段、实际写入之前再次验证输出路径。因此,即使某条路径在分派时是安全的,但到消费时已不安全,仍然会被拒绝。#[AsMessageHandler] 属性与 MessageBusInterface 的分派契约,均遵循标准的 Symfony Messenger 模型(https://symfony.com/doc/current/messenger.html)。
4d — 路由消息并运行 worker
标题为“4d — 路由消息并运行 worker”的章节在 config/packages/messenger.yaml 中,将消息路由到某个传输层:
framework: messenger: transports: async: '%env(MESSENGER_TRANSPORT_DSN)%' routing: NextPDF\Symfony\Message\GeneratePdfMessage: async然后运行一个 worker:
php bin/console messenger:consume async验证是否正常工作
标题为“验证是否正常工作”的章节php bin/console debug:container --tag=container.service_locatorphp bin/console messenger:consume async --limit=1 -vv第一条命令会确认 builder locator 已经注册。第二条命令会消费一条已排入队列的消息,并打印 handler 的进度。
后续步骤
标题为“后续步骤”的章节- /integrations/symfony/configuration/ — 调整默认值、字体与文档服务。
- /integrations/symfony/production-usage/ — 负载下 worker 的安全性与流式处理。
- /integrations/symfony/troubleshooting/ — 常见的启动与运行时问题。
符合性
标题为“符合性”的章节每一行都对应本页面提出的一项规范性主张,并锚定到一个来自受管控 SDO 语料库的完整 64 位十六进制 reference_id。provenance(来源信息)(语料库清单、检索传输方式)记录在 _sidecars/rag-citations.yaml。
| 规范 | 条款 | 参考 ID | 主张 |
|---|---|---|---|
| PSR-11 | psr_11_container#1.1.2.p4 | 容器 has()/get() 标识符契约 |
另请参阅
标题为“另请参阅”的章节- /integrations/symfony/overview/ — 功能摘要。
- /integrations/symfony/install/ — 安装与注册。
- /integrations/symfony/integration/ — 端到端集成参考。