コンテンツにスキップ

コントローラーから生成した PDF を返す

コントローラーアクション内で PDF を生成し、HTTP レスポンスとして返します。各フレームワーク統合には PdfResponse ヘルパーが用意されています。このヘルパーは、そのフレームワーク向けのレスポンスオブジェクトを構築し、Content-Type: application/pdf を設定し、セキュリティヘッダーを追加し、ファイル名をサニタイズします。このガイドでは、Laravel、Symfony、CodeIgniter 4 における 3 つの配信モード(インラインプレビュー、ファイルダウンロード、ストリーム配信)を取り上げます。

作業の途中でつまずかないよう、まず以下の前提条件を確認してください。

  • NextPDF コアがインストールされていること。
  • フレームワーク統合が 1 つインストールされており、そのサービスプロバイダー、バンドル、またはサービスが検出されていること。開始する前に、お使いのフレームワークのインストールページで検出状況を確認してください。
  • ストリームモードに追加パッケージは不要です。すべての統合で、バッファリング版に加えてストリーム版も提供されています。

これは操作手順ガイドです。お使いのフレームワークでリクエストをコントローラーにルーティングする方法は、すでに理解していることを前提としています。フレームワークごとにまず動かせる例については、「関連項目」にリンクされているフレームワークのクイックスタートをお読みください。

お使いのフレームワークに対応する統合をインストールします。以下のいずれかを実行してください。

Terminal window
composer require nextpdf/laravel
Terminal window
composer require nextpdf/symfony
Terminal window
composer require nextpdf/codeigniter

Laravel の場合は、インストール後に設定をパブリッシュします。

Terminal window
php artisan vendor:publish --tag=nextpdf-config

Symfony は Flex を通じてバンドルを自動登録し、CodeIgniter はサービスを自動検出します。続行する前に、お使いのフレームワークのインストールページで検出状況を確認してください。

すべてのフレームワーク統合は、同じ 3 つの部分から成る構造を共有します。新しいドキュメントを取得する手段、そのドキュメントにコンテンツを出力する一連の呼び出し、そして完成したドキュメントを HTTP レスポンスに変換する PdfResponse ファクトリーです。ドキュメント API(addPage()cell()setFont())はコアエンジンのサーフェスであり、フレームワーク間で同一です。各フレームワークは独自の HTTP レスポンス型を持つため、レスポンスファクトリーが返すレスポンスクラスだけが異なります。

PdfResponse は 3 つの配信モードを提供します。InlineContent-Disposition: inline ヘッダーを設定し、ブラウザーがビューアータブで PDF を表示するようにします。DownloadContent-Disposition: attachment を設定し、ブラウザーがファイルを保存するようにします。Streamed は、ドキュメント全体をメモリにバッファリングするのではなく、PDF 本体を固定サイズのチャンクで出力します。既知の Content-Length よりもピークメモリを重視する大きなドキュメントでは、これを選択してください。

フレームワークの慣用的な resolve(解決)パスを使ってドキュメントを取得します。

  • Laravel — コンテナーから NextPDF\Contracts\DocumentFactoryInterfaceapp(...) で解決し、create() を呼び出します。create() は新しい NextPDF\Core\Document を返します。これは PdfResponse ファクトリーが受け取る具象型です。
  • SymfonyNextPDF\Symfony\Service\PdfFactory を注入し、create() を呼び出します。create() は、設定済みのドキュメントデフォルトがすでに適用された新しい NextPDF\Core\Document を返します。
  • CodeIgniter 4Pdf ライブラリを Services::pdf()(または pdf() ヘルパー)で解決するか、pdf_document() で素のドキュメントを取得します。
関心事LaravelSymfonyCodeIgniter 4
新しいドキュメントapp(DocumentFactoryInterface::class)->create()PdfFactory::create()pdf_document() / Services::pdf()->document()
インラインレスポンスPdfResponse::inline($doc, $name)PdfResponse::inline($doc, $name)$pdf->inline($name) / PdfResponse::inline($doc, $name)
ダウンロードレスポンスPdfResponse::download($doc, $name)PdfResponse::download($doc, $name)$pdf->download($name) / PdfResponse::download($doc, $name)
ストリームインラインPdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)
ストリームダウンロードPdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)
戻り値の型Illuminate\Http\Response(ストリーム時: StreamedResponseSymfony\Component\HttpFoundation\Response(ストリーム時: StreamedResponseCodeIgniter\HTTP\DownloadResponse

Laravel の PdfResponseNextPDF\Laravel\Http\PdfResponse に、Symfony 版は NextPDF\Symfony\Http\PdfResponse に、CodeIgniter 版は NextPDF\CodeIgniter\Http\PdfResponse にあります。各統合のセキュリティと運用ページには、パッケージごとのレスポンス動作(ヘッダーセット、ディスポジションルール、ファイル名のサニタイズ)が網羅されています。これらのページは「関連項目」にリンクされています。

以下に、各フレームワークで最小構成のダウンロードアクションを示します。ドキュメント操作の呼び出しは同じコアサーフェスです。異なるのはコントローラーのスキャフォールディングのみです。

Laravel: app/Http/Controllers/ReportController.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Laravel\Http\PdfResponse;
final class ReportController extends Controller
{
public function download(): Response
{
$document = app(DocumentFactoryInterface::class)->create();
$document->addPage();
$document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf');
}
}
Symfony: src/Controller/ReportController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;
use NextPDF\Symfony\Service\PdfFactory;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ReportController
{
#[Route('/report', name: 'report_pdf')]
public function download(PdfFactory $pdf): Response
{
$document = $pdf->create();
$document->addPage();
$document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf');
}
}
CodeIgniter 4: app/Controllers/ReportController.php
<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;
use NextPDF\CodeIgniter\Config\Services;
final class ReportController extends BaseController
{
public function download(): DownloadResponse
{
$pdf = Services::pdf();
$pdf->document()->addPage();
$pdf->document()->cell(0, 10, 'Monthly report');
return $pdf->download('report.pdf');
}
}

ダウンロードする代わりにブラウザーでプレビューするには、Laravel と Symfony では download(...) の呼び出しを inline(...) に、CodeIgniter では $pdf->inline('report.pdf') に置き換えます。ディスポジションは inline になり、その他のヘッダーはすべて同じままです。

本番環境のアクションでは、依存関係を注入し、統合側でドキュメント化されている最も具体的な例外をキャッチし、トレースを漏らさずに失敗クラスをログに記録して、定義済みの HTTP エラーを返します。以下の例では、Laravel のコンストラクターインジェクションを使用しています。Symfony と CodeIgniter の同等の実装も同じ構造に従っており、各統合の本番利用ページに記載されています。

Laravel: app/Http/Controllers/InvoiceController.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Laravel\Http\PdfResponse;
use Psr\Log\LoggerInterface;
use Throwable;
final class InvoiceController extends Controller
{
public function __construct(
private readonly DocumentFactoryInterface $documents,
private readonly LoggerInterface $logger,
) {}
public function show(int $invoiceId): Response
{
try {
$document = $this->documents->create();
$document->addPage();
$document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
return PdfResponse::download(
$document,
"invoice-{$invoiceId}.pdf",
);
} catch (Throwable $exception) {
// Log the exception class, never the message or a stack trace,
// so internal detail does not leak into the log sink.
$this->logger->error('Invoice PDF generation failed', [
'invoice_id' => $invoiceId,
'exception' => $exception::class,
]);
return new Response('Could not generate the invoice PDF.', 500);
}
}
}

まず DocumentFactoryInterface を注入し、アクションごとに create() を呼び出します。これは新しい NextPDF\Core\Document を返します。これは Laravel の PdfResponse ファクトリーが受け取る具象型です。リクエストごとに新しいドキュメントを解決することで、テストでファクトリーを差し替え可能に保てます。単一の長時間稼働ワーカープロセス内で、無関係な 2 つのドキュメントに対して 1 つのコントローラーインスタンスを再利用しないでください。

非常に大きなドキュメントの場合は、ピークメモリを抑えるために、バッファリング版のファクトリーをストリーム版に置き換えてください。ストリーム版は StreamedResponse(Laravel と Symfony)を返し、本体を固定サイズのチャンクで出力します。これは意図的に Content-Length を省略するため、ダウンロード進捗バーや長さに依存するプロキシは既知のサイズを認識できません。小規模でレイテンシーを重視するレスポンスには、バッファリング版の download() / inline() を優先してください。

Laravel: streamed download for a large report
$document = $this->documents->create();
// ... emit content onto $document ...
return PdfResponse::streamDownload($document, 'annual-report.pdf');
  • 呼び出しごとに新しいドキュメント。 3 つの統合すべてで、ドキュメントはファクトリーから取得し、解決のたびに新しく生成されます。論理的に別のドキュメント間や、長時間稼働ワーカー内のリクエスト間で、解決済みのドキュメントをキャッシュしないでください。古いコンテンツの状態が引き継がれてしまいます。
  • 空のファイル名。 PdfResponse ファクトリーに空のファイル名を渡すと、空のディスポジションを生成するのではなく、デフォルト名(document.pdf)にフォールバックします。明示的で意味のあるファイル名を渡してください。
  • 非 ASCII ファイル名。 Laravel のレスポンスは、非 ASCII 名には自動的に RFC 5987 の filename*= パラメーターを付加し、ASCII 名にはプレーンなパラメーターを使用します。ファイル名を手動でエンコードしないでください。
  • バッファリングプロキシの背後でのストリームレスポンス。 本体全体をバッファリングするプロキシは、ストリーミングによるメモリ面の利点を打ち消してしまいます。PDF レスポンスをストリームするようにプロキシを設定するか、そのパスではバッファリングレスポンスを使用してください。
  • Symfony のストリームコールバック。 ストリーム版の Symfony は、コールバックで出力をフラッシュする StreamedResponse を返します。レスポンスを返した後で、自分でレスポンス本体に書き込まないでください。

コントローラー内での同期生成は、PDF のビルドが完了するまでリクエストをブロックします。1 ページのドキュメントであれば、一般的なリクエスト予算に十分収まります。複数ページまたはバッチ出力の場合は、キューに入れたジョブで生成をリクエストスレッドから切り離してください。キューに入れたジョブで PDF を生成するを参照してください。ストリーム版は、Content-Length が不明になる代償として、大きなドキュメントのピークメモリを削減します。メモリが制約であり、進捗バーが不要な場合に選択してください。

  • どの統合でも、PdfResponse ファクトリーは、固定セットのレスポンス強化ヘッダーを適用し、ダウンロードファイル名をサニタイズします。これらのヘッダーを自分で追加しないでください。
  • 検証されていないユーザー入力を、ファクトリーに渡すファイル名に直接埋め込まないでください。自分で制御できる値を渡したうえで、第 2 の層としてファクトリーにサニタイズさせてください。
  • catch ブロックでは、例外のメッセージやトレースではなく、例外クラスと相関識別子をログに記録してください。ログシンクに生のトレースを残すことは、情報漏洩にあたります。
  • 空の catch ブロックを書かないでください。ここで示す各例は、ログを記録し、定義済みのエラーレスポンスを返します。

各統合のセキュリティと運用ページには、統合ごとの脅威モデル(ヘッダーセット、ファイル名のサニタイズルール、ドキュメントバインディングの存続期間)が記載されています。

このガイドは、規範的な標準への主張を行いません。ここで示すすべての API 呼び出しは、名前を挙げた統合の検証済み公開サーフェスであり、各パッケージのクイックスタートおよび本番利用ページと照合済みです。「関連項目」にリンクされている上流の本番利用ページには、統合が依拠するヘッダーのセマンティクスとコンテナーバインディングの動作が、PSR の引用とともに記載されています。このクックブックページは使用方法を再掲し、規範的な引用についてはそれらのページに委ねます。