跳转到内容

NextPDF Laravel 软件包的生产环境用法

在生产环境中,请通过构造函数注入来 resolve(解析)文档契约。使用类型明确的异常处理来处理 PDF 写入失败。将繁重或批量的生成作业移交给 GeneratePdfJob,并显式接入成功与失败回调。

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

config/nextpdf.php 中配置队列连接。设置 queue.connectionqueue.queuequeue.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 接线并进行类型化错误处理的控制器”的章节
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?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)闭包会收到由容器解析出的文档,并且必须返回一份配置完成的文档。

resource: src/Laravel/Jobs/GeneratePdfJob.php
<?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() 方法,队列执行器会在终局失败时调用它。回调会封装进可序列化的闭包,因此能在队列传输中保留下来。

属性默认值配置键
tries3非由配置驱动;以子类更改
timeout120nextpdf.queue.timeout
backoff10非由配置驱动;以子类更改
队列名称pdfnextpdf.queue.queue
连接默认nextpdf.queue.connection

triesbackoff 是从任务实例读取的公开属性。随附的任务并未通过配置驱动它们。当你的重试策略不同而需要覆盖它们时,请创建 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/ ——常见的生产环境故障