NextPDF Symfony 生产环境部署
该 bundle 面向长时间运行的 PHP 运行时。文档对象是非共享的,字体注册表在预热后会被锁定,图像缓存会在请求之间重置。请以流式方式传输大型 PDF,并将繁重作业卸载到 Messenger worker。
worker 安全的服务生命周期
标题为“worker 安全的服务生命周期”的章节长时间运行的运行时会让容器在多次请求之间保持存活,因此每次请求的状态绝不能泄漏。FrankenPHP、RoadRunner 和 Messenger worker 都以这种模式运行。该 bundle 的 services.php 定义了下述生命周期,并已对照服务定义逐项核对:
- Document — 非共享。
nextpdf.document(以及PdfDocumentInterface/Document别名)每次解析都会得到一个全新的实例。在 PSR-11 下,容器对同一个 id 的每次get()都可以合法返回不同的值(PSR-11 §1.1.2)。应在每次请求时解析一个文档。绝不要跨请求持有同一个文档。 - FontRegistry — 共享且锁定。 该注册表是进程级生命周期内的单例。在
warmup()之后(当preload_fonts非空时),编译器 pass 会调用lock()。该锁可防止运行时变更,从而避免跨请求的字体状态污染。 - ImageRegistry — 共享,每次请求重置。 这个有界的最近最少使用(LRU)图像缓存是共享的,但已标记
kernel.reset标签,方法为reset,因此在遵循kernel.reset的运行时下,Symfony 会在请求之间清除它。 - EInvoice 契约 — 非共享。 当存在 Premium 实现时,embedder、validator、profile 和 schematron 服务都会注册为非共享。每次调用的解析器上下文绝不会跨请求泄漏。
推荐的注入模式
标题为“推荐的注入模式”的章节注入 PdfFactory——它是共享且无状态的配置持有者——并在每次请求时调用 create():
public function __construct(private readonly PdfFactory $pdf) {}
public function action(): Response{ $doc = $this->pdf->create(); // fresh, disposable // ... build ... return PdfResponse::inline($doc, 'document.pdf');}不要将 Document 或 nextpdf.document 注入本身共享且会跨请求存活的服务。应改为在请求作用域的方法内部解析它。
流式传输大型文档
标题为“流式传输大型文档”的章节PdfResponse::streamDownload() 和 streamInline() 返回一个 StreamedResponse。它的回调会以 64 KB 分块发送 PDF 主体,并在每个块之后刷新。这样可以为大型文档限制响应缓冲区上限。下述两项权衡均已对照 PdfResponse 进行核对:
- 流式变体会有意省略
Content-Length(响应对象无法预先知道主体大小)。下载进度条和某些 proxy 更倾向于已知长度。当文档足够小、可以保存在内存中且希望提供内容长度时,请使用非流式的download()或inline()。 - 流式变体会发送与缓冲变体相同的安全头,以及相同的
Cache-Control: private, max-age=0, must-revalidate。
对于数兆字节级的报表和批量导出,应选择流式传输。对于小型且对延迟敏感的响应,应选择缓冲变体。
大规模异步生成
标题为“大规模异步生成”的章节当请求必须快速返回,或渲染属于 CPU 密集型任务时,应将生成工作卸载到 Messenger。
- 为每种文档类型实现
PdfBuilderInterface。 - 将这些 builder 注册到
container.service_locator中,并接入GeneratePdfHandler的$builderLocator。 - 将
GeneratePdfMessage路由到一个持久化传输。 - 以有界的生命周期运行 worker。
有界的 worker 生命周期
标题为“有界的 worker 生命周期”的章节定期回收 worker,避免第三方依赖泄漏的内存分配无限增长:
php bin/console messenger:consume async \ --limit=200 \ --memory-limit=256M \ --time-limit=3600该 bundle 的 messenger.timeout 和 messenger.retries 配置键记录了每条消息预期的超时时间和重试预算。请通过 Symfony 的重试策略和 worker 标志强制执行匹配的行为。
worker 中的输出路径安全
标题为“worker 中的输出路径安全”的章节GeneratePdfMessage 会在构造时校验输出路径。随后 GeneratePdfHandler 会在执行时、写入磁盘之前再次校验该路径。这种两阶段检查对异步作业至关重要。消息在派发与消费之间可能会停留在队列中,因此 handler 不会盲目信任已入队的路径。作为纵深防御,请将 worker 的文件系统权限限制在预期输出目录内。
可观测性
标题为“可观测性”的章节其中 FontRegistry 和 ImageRegistry 服务接受可选的 Psr\Log\LoggerInterface(通过 nullOnInvalid() 绑定)。当应用提供 logger 时,这些注册表可以通过它输出诊断信息。在 PSR-3 logger 契约(PSR-3)下,该 logger 是一个可选且可替换的协作者。若需要请求级别可见性,请在你的应用代码中围绕 PdfFactory::create() 和 Messenger handler 记录日志。在事故排查期间,请使用 messenger:consume -vv。
部署检查清单
标题为“部署检查清单”的章节- 在应用的
composer.json中固定单一nextpdf/core主版本 (该 bundle 支持^3.0 || ^5.2)。 - 确保部署的 PHP 镜像已启用
ext-mbstring和ext-zlib(否则该 bundle 会在启动时快速失败)。 - 将你的文档使用的字体预先填入
preload_fonts,让注册表在启动时预热并锁定,而不是等到首次请求时才预热。 - 如果你需要在多次部署之间复用已缓存的工件,请将
cache_path指向可写且持久的位置。否则使用%kernel.cache_dir%默认值即可。 - 在部署期间运行
php bin/console cache:warmup,以便在流量到来之前构建好已编译的容器(包括可选扩展的探测)。 - 生产环境中的异步工作应使用持久化的 Messenger 传输(而非
sync),并使用--limit/--memory-limit/--time-limit来回收 worker。
边界情况与注意事项
标题为“边界情况与注意事项”的章节- 带缓冲 proxy 后面的流式响应 —— 会缓冲完整主体的 proxy 会抵消内存优势。请将该 proxy 配置为流式传输 PDF 响应,或在该位置改用缓冲响应。
kernel.reset未被遵循 —— 在不调用kernel.reset的运行时下,图像缓存受image_cache_mb限定,但不会在请求之间清除;请据此设置容量上限。- 跨请求持有文档 —— 在先前请求中捕获的
Document会携带过期状态。请始终在每次请求时通过PdfFactory解析它。
合规性
标题为“合规性”的章节每一行都对应本页面作出的一项规范性声明,并固定到受控 SDO 语料库中的完整 64 位十六进制 reference_id。来源信息(即语料库清单和检索传输)位于 _sidecars/rag-citations.yaml 中。
| 规范 | 条款 | 参考 ID(reference_id) | 声明 |
|---|---|---|---|
| PSR-11 | psr_11_container#1.1.2.p3.b | 非共享服务:每次解析得到不同的值 | |
| PSR-3 | psr_3_logger#x3.p17 | 可选的 logger 协作者 |
另请参阅
标题为“另请参阅”的章节- /integrations/symfony/configuration/ —— 服务生命周期与参数。
- /integrations/symfony/security-and-operations/ —— 响应头、路径校验、密钥处理。
- /integrations/symfony/troubleshooting/ —— 启动与运行时诊断。
- /integrations/symfony/quickstart/ —— 最简异步配置。