Laravel 开发者指南
Laravel 包让 NextPDF 遵循 Laravel 惯例,同时不改动核心文档生命周期。容器(container)拥有共享的注册表(registry)与工厂(factory)。每份 PDF 文档都是一次性使用的,只应被构建、返回、流式传输或保存一次。
当你围绕 nextpdf/laravel 设计应用程序服务、队列作业(queue job)、响应流程或测试覆盖时,请参考本指南。
架构边界
标题为“架构边界”的章节| 层级 | 拥有者 | 职责 | 不要放在这里 |
|---|---|---|---|
| 控制器(controller) | 应用程序 | 授权请求、选择文档构建器(builder),并返回响应。 | 跨使用场景共享的 PDF 版面规则。 |
| 应用程序服务 | 应用程序 | 收集领域数据,并调用构建文档的代码。 | 容器启动逻辑或包配置。 |
| 文档构建器(builder) | 应用程序 | 将领域数据转换为 NextPDF 文档调用。 | 请求对象、Eloquent 查询逻辑,或队列传输(transport)细节。 |
| Laravel 集成 | nextpdf/laravel | 绑定工厂、注册表、签名器(signer)、TSA 客户端、facade、响应与队列作业。 | 业务特定的存储路径或租户策略。 |
| 核心引擎 | nextpdf/nextpdf | 构建并序列化 PDF。 | Laravel 响应、队列或文件系统策略。 |
运行期生命周期
标题为“运行期生命周期”的章节| 阶段 | 行为 | 开发者动作 |
|---|---|---|
| 服务提供者注册 | NextPdfServiceProvider::register() 会注册共享注册表、文档工厂、文档绑定、HTTP 客户端、TSA 客户端、签名器,以及可选的电子发票契约。 | 进入生产环境前,请先发布并检查 config/nextpdf.php。 |
| resolve(解析)文档 | Pdf facade 与 PdfDocumentInterface 绑定会通过 DocumentFactoryInterface 解析出一份全新文档。 | 每个请求、命令或队列作业只解析一次文档。 |
| 撰写 | 应用程序代码会通过 facade 或注入的文档,调用核心文档 API。 | 请把领域数据的提取保持在文档构建器之外。 |
| 终端输出 | PdfResponse 会输出 HTTP 内容,或将文档保存到磁盘。 | 每份文档只选择一条终端输出路径。 |
| 队列执行 | GeneratePdfJob 会在 worker 内重新构建文档,并再次验证输出路径。 | 传入标量上下文数据,并让回调(callback)保持幂等。 |
建议的应用程序结构
标题为“建议的应用程序结构”的章节| 路径 | 用途 |
|---|---|
app/Pdf/Builders/* | 纯文档构建器。它们接收数据并返回一份已完成的文档。 |
app/Pdf/Data/* | 承载已授权文档输入的小型 DTO。 |
app/Services/* | 应用程序编排、查询、授权交接,以及存储路径选择。 |
app/Jobs/* | 当应用程序需要具名作业时,围绕 GeneratePdfJob 的可选包装器。 |
tests/Feature/Pdf/* | HTTP 响应、队列派发与授权测试。 |
tests/Unit/Pdf/* | 以小型、具确定性输入进行的构建器测试。 |
让构建器与 Laravel 请求对象保持独立。构建器应该能用相同的输入,从控制器、命令、测试与队列 worker 中调用。
<?php
namespace App\Pdf\Builders;
use App\Pdf\Data\InvoicePdfData;use NextPDF\Contracts\PdfDocumentInterface;
final readonly class InvoicePdfBuilder{ public function build(PdfDocumentInterface $pdf, InvoicePdfData $data): PdfDocumentInterface { $pdf->setTitle($data->title) ->addPage() ->setFont('dejavusans', '', 12) ->writeHtml($data->html);
return $pdf; }}同步响应模式
标题为“同步响应模式”的章节当 PDF 流程属于应用程序逻辑的一部分时,请使用构造函数注入。只有在简短的控制器流程中,且静态写法能提升可读性时,才使用 facade。
<?php
namespace App\Http\Controllers;
use App\Pdf\Builders\InvoicePdfBuilder;use App\Pdf\Data\InvoicePdfData;use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Http\PdfResponse;
final readonly class DownloadInvoiceController{ public function __invoke( PdfDocumentInterface $pdf, InvoicePdfBuilder $builder, ) { $document = $builder->build( $pdf, InvoicePdfData::fromInvoiceId(1234), );
return PdfResponse::download($document, 'invoice-1234.pdf'); }}响应辅助程序会在构建 Laravel 响应之前,先生成文档字节。它们是响应辅助程序,不是后台 renderer(渲染器)。
队列模式
标题为“队列模式”的章节GeneratePdfJob 接受一个构建器可调用项与输出路径。该作业会在运行期验证不安全的路径。应用程序代码仍应在派发前,先选定对租户安全的存储根目录。
<?php
use App\Pdf\Builders\QueuedInvoiceBuilder;use NextPDF\Laravel\Jobs\GeneratePdfJob;
GeneratePdfJob::dispatch( outputPath: storage_path('app/pdfs/invoice-1234.pdf'), builder: [QueuedInvoiceBuilder::class, 'build'],)->onQueue(config('nextpdf.queue.queue', 'pdf'));队列回调应该保持精简。与其把复杂的闭包(closure)存进队列负载,更建议由应用程序的作业监听器写入持久状态。
扩展点
标题为“扩展点”的章节| 扩展点 | 用途 | 限制 |
|---|---|---|
PdfDocumentInterface 绑定 | 替换或装饰文档创建流程,以应用整个应用程序的默认值。 | 必须返回一个全新的文档实例。 |
DocumentFactoryInterface | 在服务与测试中明确创建全新文档。 | 不要缓存返回的文档。 |
config/nextpdf.php | 默认值、队列设置、Chrome renderer(渲染器)设置、签名挂钩、TSA、OCSP 缓存。 | 请把环境变量视为部署配置,而不是请求输入。 |
GeneratePdfJob 构建器 | 以异步方式构建文档。 | 可调用项必须能被 Laravel 的队列传输序列化。 |
| 成功/失败回调 | 生成后的通知或清理。 | 让回调保持幂等并留意副作用。 |
| 可选的 Premium 契约 | 电子发票嵌入器、验证器、配置文件与 Schematron 执行器。 | 只在已安装且已授权可选包的地方解析。 |
开发工作流程
标题为“开发工作流程”的章节- 先在控制器或功能测试中同步构建第一份文档。
- 把版面代码搬进
app/Pdf/Builders底下的构建器类。 - 把查询与授权逻辑搬进应用程序服务。
- 为
PdfResponse加上标头与文件名的测试。 - 把缓慢或大批量的生成作业移到
GeneratePdfJob。 - 为序列化上下文、输出路径策略与失败处理加上队列测试。
- 使用具代表性的生产环境数据测量内存与 render 时间。
失败处理
标题为“失败处理”的章节| 失败 | 应该在哪里处理 | 建议的响应 |
|---|---|---|
| 无效请求或未授权文档 | 控制器或策略。 | 返回常规应用程序的授权或验证响应。 |
| 缺少字体或图像无效 | 构建器测试与应用程序日志记录。 | 让请求或作业失败;不要输出不完整的 PDF。 |
| 不安全的输出路径 | 应用程序存储服务与 GeneratePdfJob。 | 在派发前就拒绝,并以 worker 端验证作为纵深防御。 |
| 签名或 TSA 失败 | 签名服务边界。 | 决定文档是否允许未签名;对受监管的文档,默认采用 fail closed(失败即拒)。 |
| 队列超时 | 队列 worker 配置与可观测性。 | 只有在构建器具确定性,且输出路径可安全覆盖时,才重试。 |
安全的默认值
标题为“安全的默认值”的章节| 关注点 | 默认值 | 何时覆盖 |
|---|---|---|
| 队列名称 | pdf | 当生成作业会与面向用户的作业争用资源时,请使用专用队列。 |
| 作业超时 | 120 秒 | 只有在测量过文档大小与 worker 容量后,才调高。 |
| 响应文件名 | document.pdf | 使用经过净化的业务标识符。 |
| 字体注册表 | 共享,并在预热后锁定。 | 为热路径上使用的字体加入 preload_fonts。 |
| 图像注册表 | 共享的有界缓存。 | 在内存受限的 worker 上调低 image_cache_mb。 |
| 流式响应分块 | 64 KB 的区块。 | 不要依赖区块边界;那只是输出细节。 |
测试检查清单
标题为“测试检查清单”的章节- 控制器测试要断言
Content-Type、Content-Disposition与防御性标头。 - 构建器测试使用具确定性的 DTO,并且不查询数据库。
- 队列测试要断言构建器收到的是一份全新文档。
- 路径测试涵盖路径遍历、stream-wrapper、null 字节,以及非
.pdf的拒绝。 - worker 测试要在与生产环境相同的内存上限下,render 具代表性的文档。
- 可选的签名测试要涵盖缺少证书、密码错误、TSA 无法使用,以及已配置的签名级别。