Symfony 开发者指南
Symfony 包采用 service 优先的设计。注入 PdfFactory,为每份文档调用 create(),并通过 Messenger builder 处理异步生成。由于每次调用都会返回一份全新的文档,这个 factory 可以作为 container service 注册。
当你设计 controller、service、Messenger handler,或围绕 nextpdf/symfony 的 bundle 层扩展点时,请参考本指南。
架构边界
标题为“架构边界”的章节| 层级 | 负责方 | 职责 | 不要放这里 |
|---|---|---|---|
| Controller(控制器) | 应用程序 | 对请求进行授权、收集输入,并返回 PdfResponse。 | 跨多个使用场景复用的 PDF 版式。 |
| 应用程序 service | 应用程序 | 加载领域数据,并选择一个 builder。 | Symfony container 编译逻辑。 |
| Builder service(构建器服务) | 应用程序 | 实现 PdfBuilderInterface,用于同步构建或入队后的文档构建。 | 请求对象、entity manager 或不可序列化的 context。 |
| Symfony bundle(包) | nextpdf/symfony | 注册 service、config tree、可选的 extension pass、响应辅助工具和 Messenger DTO。 | 租户专属的存储策略。 |
| 核心引擎 | nextpdf/nextpdf | 构建并序列化文档。 | Symfony 响应或 Messenger 行为。 |
运行期生命周期
标题为“运行期生命周期”的章节| 阶段 | 行为 | 开发者动作 |
|---|---|---|
| Bundle 启动 | NextPdfBundle::build() 会注册对可选扩展功能的检测。 | 让 Symfony 自动发现该 bundle,或在 bundles.php 中手动注册。 |
| 加载配置 | NextPdfExtension::load() 会处理 nextpdf: 配置并加载 service 定义。 | 保持配置明确,并让其感知环境。 |
| 使用 factory | PdfFactory::create() 会返回一份全新且已完成配置的文档。 | 不要把文档保存在 service 中。 |
| Controller 输出 | PdfResponse 会将完成的文档转换为响应。 | 使用此辅助工具,不要手动组装标头。 |
| Messenger 派发 | GeneratePdfMessage 会携带 builder 类、输出路径和可序列化的 context。 | 让 context 保持精简,并以标量为主。 |
| 处理消息 | GeneratePdfHandler 会通过 service locator 解析 builder 并保存文档。 | 保持 builder 的确定性与幂等性。 |
建议的应用程序结构
标题为“建议的应用程序结构”的章节| 路径 | 用途 |
|---|---|
src/Pdf/Builder/* | 实现 PdfBuilderInterface 的 service。 |
src/Pdf/Data/* | 用作 builder context 的小型 DTO 或数组。 |
src/Pdf/Storage/* | 存储根目录的选择与输出文件名策略。 |
src/Controller/* | 同步响应入口。 |
tests/Pdf/* | builder、响应、Messenger 和配置测试。 |
优先使用 builder service,而不是静态辅助函数。它们更易于标记、装饰和测试,也便于从 Messenger 中使用。
<?php
namespace App\Pdf\Builder;
use NextPDF\Core\Document;use NextPDF\Symfony\Message\PdfBuilderInterface;
final readonly class InvoicePdfBuilder implements PdfBuilderInterface{ public function build(Document $document, array $context): Document { $document->setTitle((string) $context['title']) ->addPage() ->writeHtml((string) $context['html']);
return $document; }}同步响应模式
标题为“同步响应模式”的章节<?php
namespace App\Controller;
use App\Pdf\Builder\InvoicePdfBuilder;use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;
final readonly class InvoiceController{ public function __invoke( PdfFactory $factory, InvoicePdfBuilder $builder, ) { $document = $builder->build($factory->create(), [ 'title' => 'Invoice 1234', 'html' => '<h1>Invoice 1234</h1>', ]);
return PdfResponse::download($document, 'invoice-1234.pdf'); }}让 controller 的 context 保持精简。如果某个 builder 需要大量领域对象,请把这段协调逻辑移到应用程序 service,再将 DTO 或规范化后的数组传给 builder。
Messenger 模式
标题为“Messenger 模式”的章节GeneratePdfMessage 会在派发前验证 builder 类和输出路径。handler 会在执行时再次验证该路径。
<?php
use App\Pdf\Builder\InvoicePdfBuilder;use NextPDF\Symfony\Message\GeneratePdfMessage;
$bus->dispatch(new GeneratePdfMessage( builderClass: InvoicePdfBuilder::class, outputPath: $projectDir . '/var/pdfs/invoice-1234.pdf', builderContext: [ 'title' => 'Invoice 1234', 'html' => '<h1>Invoice 1234</h1>', ],));不要把 Doctrine entity、打开的流、closure、请求对象或 service 对象放入 builderContext。
扩展点
标题为“扩展点”的章节| 扩展点 | 适用场景 | 限制 |
|---|---|---|
PdfFactory service 装饰 | 在文档到达 controller 之前应用应用程序层面的默认值。 | 必须保留全新文档的语义。 |
PdfBuilderInterface | 定义可入队或可复用的文档 builder。 | 必须返回一个 Document。 |
OptionalExtensionPass | 在编译期启用可选的 Artisan 或 Premium 功能。 | 可用与否取决于 container 编译状态,而非请求状态。 |
| Symfony config tree(配置树) | 默认值、PDF/A、renderer 设置、签名、TSA、Messenger。 | 无效的配置应在 container 构建时就失败。 |
GeneratePdfHandler service 接线 | 限制排队消息可访问的 builder。 | service locator 只应暴露已核准的 builder service。 |
开发流程
标题为“开发流程”的章节- 新增一个输入具备确定性的 builder service。
- 在 controller 或 service 中使用
PdfFactory::create()。 - 针对文件名、内容类型和标头新增响应测试。
- 当同一份文档必须异步生成时,为 Messenger 注册该 builder。
- 针对类名、输出路径和 context 结构,新增无效消息测试。
- 使用最小配置和生产环境配置,新增一个 container 编译测试。
- 在与生产环境相同的 PHP 设置下,测量 render 时间和内存用量。
失败处理
标题为“失败处理”的章节| 失败 | 应在何处处理 | 建议的响应 |
|---|---|---|
| 无效配置 | Container 编译。 | 在流量到达应用程序之前使部署失败。 |
| 缺少 builder service | Messenger handler 测试与 service tag。 | 让消息失败,并通知负责团队。 |
| 不安全的输出路径 | 消息构造函数与存储策略。 | 在派发前拒绝;handler 验证保留为纵深防御。 |
| 可选扩展功能不可用 | compiler pass 与 factory 行为。 | 停用可选功能,或明确要求安装它。 |
| service 转换或 render 失败 | builder 边界。 | 除非该使用场景有书面记录的回退机制,否则一律 fail closed。 |
安全的默认值
标题为“安全的默认值”的章节| 关注点 | 默认值 | 何时覆盖 |
|---|---|---|
| Factory 生命周期 | Container service(容器服务)。 | 保留此设置;factory 是安全的,因为它只负责创建文档。 |
| 文档生命周期 | 一个工作单元。 | 绝不要跨请求或跨消息共用。 |
| 输出路径验证 | 消息构造函数与 handler。 | 在应用程序代码中添加租户或存储根目录限制。 |
| 响应文件名 | document.pdf。 | 以净化后的业务标识符覆盖。 |
| Messenger transport(Messenger 传输) | async。 | 当 PDF 工作量很重时,使用专属 transport。 |
测试检查清单
标题为“测试检查清单”的章节- container 测试会使用最小配置和生产环境配置编译此 bundle。
- 响应测试会验证安全标头和文件名处理。
- Messenger 测试会验证无效路径和无效 builder 类名在派发前失败。
- handler 测试会使用真实 builder service 和临时输出目录。
- builder 测试会 render 一份有代表性的文档,并在类似生产环境的文件系统权限下保存它。
- 可选扩展功能测试涵盖 Artisan 不可用、Premium 不可用以及已配置的 PDF/A profile 行为。