コンテンツにスキップ

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 を設定します。そのうえで、構成した接続に対してワーカーが実行されていることを確認します。

コンテナーは NextPDF\Contracts\PdfDocumentInterface をファクトリーバインディングとして公開します。解決のたびに、新しい NextPDF\Core\Document が生成されます。PSR-11 では、バインディング戦略によっては、コンテナーが連続する get() 呼び出しで異なる値を返すことが許容されています(PSR-11 §1.1.2)。このパッケージがここでファクトリーバインディングを使用するのは、リクエストスコープの可変状態がリクエストをまたいで持ち越されないようにするためです。フォントレジストリと画像レジストリはシングルトンです。これにより、バインドされた識別子は登録されたエントリに解決されるというコントラクト(PSR-11 §1.1.2)を維持しつつ、コストの高いリソースをワーカー全体で共有できます。

本番環境のコードでは、ファサードよりもコンストラクター注入を優先します。これにより依存関係が明示され、ファサードルートを起動せずにコントローラーをユニットテスト可能な状態に保てます。

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 は使用しないでください。コンテナーは、コントローラーのインスタンス化ごとに新しいドキュメントを返します。同一プロセス内では、無関係な 2 つのドキュメントに同じコントローラーインスタンスを再利用しないでください。

catch ブロックでは例外クラスをログに記録し、スタックトレースを漏えいさせる代わりに、定義済みの HTTP エラーを返します。Psr\Log\LoggerInterface を使用してください。これはコンテナーによってフレームワークのロガーに解決されます。PSR-3 では、プレースホルダーのエスケープは実装者に委ねられており、呼び出し側はコンテキスト値を事前にエスケープしないよう求められています(PSR-3 §1.2)。補間した文字列ではなく、構造化されたコンテキストを渡してください。

GeneratePdfJobShouldQueue ジョブです。デフォルトは、試行回数が 3 回、タイムアウトが 120 秒、バックオフが 10 秒です。これら 3 つはいずれも config/nextpdf.php でオーバーライドできます。ビルダークロージャーは、コンテナーが解決したドキュメントを受け取り、構成済みのドキュメントを返す必要があります。

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) に転送します。そのため、成功・失敗コールバックは、キューに入れられるのと同じジョブに結び付けられます。これは /integrations/laravel/quickstart/. の位置引数形式の GeneratePdfJob::dispatch($path, $builder) と一致します。成功コールバックは出力パスを受け取り、失敗コールバックは Throwable を受け取ります。このジョブは、チェーン用にジョブを返すフルエントな then() および catch() セッターも公開します。これらのセッターは、たとえば dispatch() ヘルパーを介して同じインスタンスを保持したままディスパッチする場合にのみ使用してください。このジョブは failed() メソッドも公開しており、終端的な失敗時にキューランナーから呼び出されます。コールバックはシリアライズ可能なクロージャーでラップされており、キュートランスポートをまたいでも保持されます。

プロパティデフォルト設定キー
tries3設定では制御不可。変更するにはサブクラス化
timeout120nextpdf.queue.timeout
backoff10設定では制御不可。変更するにはサブクラス化
キュー名pdfnextpdf.queue.queue
接続デフォルトnextpdf.queue.connection

triesbackoff は、ジョブインスタンスから読み取られる public プロパティです。同梱のジョブでは、これらを設定から制御しません。別のリトライポリシーに合わせてこれらをオーバーライドするには、GeneratePdfJob をサブクラス化します。

  • ビルダークロージャーは PdfDocumentInterface を返す必要があります。ジョブは、最初に解決されたインスタンスではなく、その戻り値を保存します。ジョブのテストは、このコントラクトを明示的にアサートします。
  • コンテナーが SignerInterface を解決すると、署名が有効で、証明書が構成されており、かつ nextpdf/premium がインストールされている場合を除き、null が返されます。署名の前に、常に null チェックを行ってください。
  • 長時間稼働するワーカー(Octane/RoadRunner/Swoole)は、ロックされたフォントレジストリを共有します。ウォームアップが最初のリクエスト時ではなくワーカー起動時に一度だけ行われるよう、preload_fonts を構成します。
  • 失敗したジョブは failed() を呼び出します。これは tries を使い切った後に行われます。各試行の失敗時には、キューランナーが終端的な失敗を宣言するまで onFailure は呼び出されません。

コントローラー内で同期生成すると、PDF のビルドが完了するまでリクエストがブロックされます。複数ページの出力やバッチ出力では、GeneratePdfJob をディスパッチして即座に返します。シングルトンのレジストリは、フォントの解析と画像のデコードをワーカーのライフタイム全体に分散させます。これにより、リクエストごとのコストはドキュメントの構築とコンテンツの出力に限定されます。

DI コントローラーは、内部の詳細がログに漏えいするのを避けるため、例外のメッセージやトレースではなく例外クラスをログに記録します。GeneratePdfJob は、キュートランスポート上で改ざんされたシリアライズ済みペイロードの影響を抑えるため、ワーカー側で出力パスを検証します。詳細は /integrations/laravel/security-and-operations/. を参照してください。

主張出典条項リファレンス ID
バインドされた識別子は登録されたエントリに解決されるPSR-11 コンテナー§1.1.2
連続する解決は、バインディング戦略(ファクトリーバインディング)によって異なる場合があるPSR-11 コンテナー§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/ — 本番環境でよくある障害