NextPDF Laravel 软件包的生产环境用法
在生产环境中,请通过构造函数注入来 resolve(解析)文档契约。使用类型明确的异常处理来处理 PDF 写入失败。将繁重或批量的生成作业移交给 GeneratePdfJob,并显式接入成功与失败回调。
composer require nextpdf/laravelphp artisan vendor:publish --tag=nextpdf-config在 config/nextpdf.php 中配置队列连接。设置 queue.connection、queue.queue 与 queue.timeout。接着确认已有 worker 正在为配置的连接运行。
概念总览
标题为“概念总览”的章节容器将 NextPDF\Contracts\PdfDocumentInterface 公开为工厂(factory)绑定。每次解析都会产生一个全新的 NextPDF\Core\Document。PSR-11 允许容器依据绑定策略,在连续的 get() 调用中返回不同的值(PSR-11 §1.1.2)。本软件包在这里采用工厂绑定,确保请求作用域内的可变状态绝不会跨请求泄漏。字体与图像注册表则是单例(singleton)。这样既能维持「绑定的标识符会解析到其注册项目」这项契约(PSR-11 §1.1.2),又能在整个 worker 生命周期内共享这些昂贵资源。
在生产环境代码中,优先采用构造函数注入,而不是 facade。这样依赖关系更明确,也让控制器无须启动 facade 根节点即可进行单元测试。
代码示例——生产环境
标题为“代码示例——生产环境”的章节采用 DI 接线并进行类型化错误处理的控制器
标题为“采用 DI 接线并进行类型化错误处理的控制器”的章节<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly PdfDocumentInterface $document, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $this->document->addPage(); $this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true); $this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download( $this->document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Rethrow as an HTTP-meaningful failure; never swallow. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}请注入 PdfDocumentInterface,而非具体的 Document,这样在测试中仍可替换绑定。容器会在每次实例化控制器时返回一份全新的文档。不要在同一个进程内使用同一个控制器实例处理两份不相关的文档。
catch 块会记录异常类,并返回已定义的 HTTP 错误,同时不会泄漏堆栈跟踪。使用 Psr\Log\LoggerInterface 时,容器会将它解析为 Framework(框架)的记录器。PSR-3 把 placeholder(占位符)转义交给实现者处理,并要求调用端不要预先转义上下文值(PSR-3 §1.2)。请传入结构化上下文数据,而不是插值后的字符串。
带成功与失败回调的队列生成流程
标题为“带成功与失败回调的队列生成流程”的章节GeneratePdfJob 是一个 ShouldQueue 任务。它默认为三次尝试、120 秒超时,以及 10 秒退避。你可以通过 config/nextpdf.php 覆盖这三者。构建器(builder)闭包会收到由容器解析出的文档,并且必须返回一份配置完成的文档。
<?php
declare(strict_types=1);
namespace App\Jobs;
use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Jobs\GeneratePdfJob;use Psr\Log\LoggerInterface;use Throwable;
final class DispatchMonthlyStatement{ public function __construct(private readonly LoggerInterface $logger) {}
public function __invoke(int $accountId): void { // Dispatchable::dispatch() is `public static`: it constructs the // job from the arguments it receives and returns a PendingDispatch. // Pass every constructor argument — including the callbacks — to // the static call. Building an instance and then calling // `$job->dispatch(...)` would discard that instance (and its // callbacks) and queue a different job from only the static args. GeneratePdfJob::dispatch( storage_path("app/statements/{$accountId}.pdf"), static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document ->addPage() ->cell(0, 10, "Statement for account {$accountId}", newLine: true), function (string $path) use ($accountId): void { $this->logger->info('Statement PDF written', [ 'account_id' => $accountId, 'path' => $path, ]); }, function (Throwable $exception) use ($accountId): void { $this->logger->error('Statement PDF failed', [ 'account_id' => $accountId, 'exception' => $exception::class, ]); }, ); }}GeneratePdfJob::dispatch() 会把它的参数直接转发给构造函数 (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure)。因此成功与失败回调会绑定到实际入队的那个任务上。这与 GeneratePdfJob::dispatch($path, $builder) 在 /integrations/laravel/quickstart/. 中的位置参数形式一致。成功回调会收到输出路径,失败回调则会收到 Throwable。这个任务也提供流畅式的 then() 与 catch() 设置方法,返回任务本身以便链式调用。只有在你保留并派发同一个实例时才使用这些设置方法,例如通过 dispatch() 辅助函数。这个任务也提供 failed() 方法,队列执行器会在终局失败时调用它。回调会封装进可序列化的闭包,因此能在队列传输中保留下来。
调优队列行为
标题为“调优队列行为”的章节| 属性 | 默认值 | 配置键 |
|---|---|---|
tries | 3 | 非由配置驱动;以子类更改 |
timeout | 120 | nextpdf.queue.timeout |
backoff | 10 | 非由配置驱动;以子类更改 |
| 队列名称 | pdf | nextpdf.queue.queue |
| 连接 | 默认 | nextpdf.queue.connection |
tries 与 backoff 是从任务实例读取的公开属性。随附的任务并未通过配置驱动它们。当你的重试策略不同而需要覆盖它们时,请创建 GeneratePdfJob 的子类。
边界情况与陷阱
标题为“边界情况与陷阱”的章节- 构建器闭包必须返回一个
PdfDocumentInterface。任务保存的是这个返回值,而非一开始解析出的实例。任务测试会明确断言这项契约。 - 解析
SignerInterface会返回null,除非已启用签名、已配置证书,且已安装nextpdf/premium。签名前务必先做 null 检查。 - 长生命周期的 worker(Octane/RoadRunner/Swoole)会共享已锁定的字体注册表。请配置
preload_fonts,让预热在 worker 启动时发生一次,而不是延迟到第一个请求才发生。 - 任务失败后会调用
failed(),也就是在用尽tries之后。在队列执行器宣告终局失败之前,单次尝试的失败并不会调用onFailure。
在控制器中同步生成 PDF,会在整个构建期间阻塞请求。对于多页或批量输出,请派发 GeneratePdfJob 并立即返回。单例注册表会将字体解析与图像解码的成本分摊到整个 worker 生命周期。这样一来,每个请求的成本就只限于文档构建与内容输出。
安全注意事项
标题为“安全注意事项”的章节DI 控制器只记录异常类,而不记录消息或跟踪,以免内部细节泄漏进日志。GeneratePdfJob 会在 worker 上验证输出路径,以降低队列传输过程中序列化载荷被篡改所带来的风险。完整内容请见 /integrations/laravel/security-and-operations/.
符合性
标题为“符合性”的章节| 主张 | 来源 | 条款 | 参考 ID |
|---|---|---|---|
| 绑定的标识符会解析到其注册项目 | PSR-11 Container(容器) | §1.1.2 | |
| 连续解析的结果可能因绑定策略而不同(工厂绑定) | PSR-11 Container 容器 | §1.1.2 |
PSR-3 的日志指引记载于 PSR-3 规范中。该指引指出,占位符转义是实现者的责任,且调用端应传入结构化上下文数据。请见文档 psr_3_logger §1.2。
商业场景
标题为“商业场景”的章节通过 nextpdf/premium 产出的已签名 PAdES B-B 输出与 PDF/A 归档,使用的是同一套 DI 接口。这是一项可选的 Enterprise 能力。这里所记载的 Core 软件包,无需任何代码更改即可采用它。请见 https://nextpdf.dev/get-license/?intent=laravel-signing。
另请参阅
标题为“另请参阅”的章节- /integrations/laravel/quickstart/ ——最精简的第一个示例
- /integrations/laravel/configuration/ ——队列、签名与字体相关的配置键
- /integrations/laravel/security-and-operations/ ——威胁模型与加固
- /integrations/laravel/troubleshooting/ ——常见的生产环境故障