コンテンツにスキップ

契約 / ドキュメント

ドキュメントドメインには、PDF を構築するための契約が集約されています。コンテンツを扱う PdfDocumentInterface、ワーカーセーフな生成を担う DocumentFactoryInterface、フォントと画像のレジストリ契約、そして配信とレイアウトに関する 3 つの enum が含まれます。いずれも 1.0.0 または 1.7.0 以降 stable です。

Terminal window
composer require nextpdf/core:^3

PdfDocumentInterface は主要な API サーフェスです。ページ管理、フォント選択、セルおよびマルチセルのテキストレイアウト、HTML レンダリング、画像の埋め込み、そして最終出力を定義します。すべてのメソッドが static を返すため、呼び出しをチェーンできます。Document::createStandalone() は、このインターフェイスを満たす具象インスタンスを返します。独自のサービスでは、このインターフェイスを型ヒントとして指定してください。これにより、エンジン内部を差し替え可能な状態に保てます。

ドキュメントの生成には 2 つの経路があります。従来の PHP-FPM リクエストでは、createStandalone() がプライベートなレジストリを備えた自己完結型のドキュメントを構築します。長時間稼働するワーカーは、もう一方の経路を使用します。これには RoadRunner、Swoole、Laravel Octane が含まれます。そこでは、DocumentFactoryInterface::create() が使い捨ての新しい Document を返します。ドキュメントはプロセス存続期間のレジストリから読み取りますが、それらを変更することはありません。ファクトリは FontRegistryInterfaceImageRegistryInterface のシングルトンを保持します。各ドキュメントは、それぞれ専用のレンダリングコンテキストとライターを取得します。これは障害を封じ込めるための設計です。あるドキュメントが、別のドキュメントの依存する共有状態を破損させることはできません。

ワーカーが高速性を保てる理由は、レジストリ契約にあります。FontRegistryInterface はフォントファイルを一度だけ解析します。解析したメタデータは、プロセスの存続期間にわたってキャッシュされます。ウォームアップ後にロックできるため、本番トラフィック中に変更されることはありません。ImageRegistryInterface は、上限付きの最長未使用(LRU)ポリシーに従って、デコード済みの画像バイナリデータをキャッシュします。画像のメタデータは、バイナリが破棄された後もメモリに常駐し続けます。どちらもキャパシティプランニング向けに memoryUsage() を公開します。ImageRegistryInterfaceResettableService を継承します。この契約により、構造メタデータを破棄することなくキャッシュ済みデータを退避できます。ワーカーは、メモリ圧迫時に画像キャッシュを破棄しつつ処理を継続できます。

3 つの enum がこのドメインを補完します。OutputDestination は、インライン表示、強制ダウンロード、ファイルシステムへの書き込み、生の文字列としての返却のいずれかを選択します。Orientation は、縦向きまたは横向きを選択します。Alignment は、テキストの左揃え、中央揃え、右揃え、両端揃えのいずれかを選択します。各 enum は、enum 値としてレガシーの TCPDF コードを各ケースに割り当てています。そのため、compat-tcpdf ブリッジは自然にマッピングできます。これらの enum に対する後方互換性の保証は、追加のみを行うものです。どのケースも削除されることはありません。新しいケースはマイナーリリースで追加される場合があります。

種別主なメンバー安定性対応バージョン
PdfDocumentInterfaceinterfaceaddPage(), setMargins(), setFont(), cell(), multiCell(), writeHtml(), image(), output(), save()stable1.0.0
DocumentFactoryInterfaceinterfacecreate(?Config): Documentstable1.7.0
ResettableServiceinterfacereset(): voidstable1.7.0
FontRegistryInterfaceinterfaceregister(), get(), warmup(), lock(), isLocked(), registerFromBinary(), memoryUsage()stable1.7.0
ImageRegistryInterfaceinterfaceload(), loadFromString(), getMetadata(), memoryUsage()ResettableService を継承)stable2.0.0
OutputDestinationenum (string)Inline, Download, File, Stringstable1.0.0
Orientationenum (string)Portrait, Landscapestable1.0.0
Alignmentenum (string)Left, Center, Right, Justifystable1.0.0

FontRegistryInterfaceImageRegistryInterface については、タイポグラフィのページで詳しく説明しています。このドキュメントページでは、生成ライフサイクルにおける両者の役割を扱います。

examples/01-hello-world.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Hello World');
$doc->addPage();
$doc->setFont('helvetica', '', 24);
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'This is a minimal PDF generated with NextPDF.', newLine: true);
$doc->save(__DIR__ . '/output/01-hello-world.pdf');
examples/02-pdf-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\PdfFactory;
use NextPDF\ValueObjects\{Margin, PageSize};
$factory = PdfFactory::new()
->withPageSize(PageSize::A4())
->withMargins(new Margin(15.0, 15.0, 15.0, 15.0))
->withCompress(true)
->withLang('en');
// The same configured factory creates independent documents.
$doc = $factory->create();
$doc->setTitle('PdfFactory Example');
$doc->setAuthor('NextPDF');
$doc->addPage();
$doc->setFont('helvetica', '', 16);
$doc->cell(0, 12, 'Created via PdfFactory', newLine: true);
$doc2 = $factory->create();
$doc2->addPage();
$doc2->setFont('helvetica', '', 12);
$doc2->cell(0, 10, 'Second document from the same factory.');
$doc->save(__DIR__ . '/output/02-pdf-factory.pdf');

PdfFactory はイミュータブルなビルダーであり、各 with*() は新しいインスタンスを返します。内部では DocumentFactoryInterface を組み合わせているため、概要で説明したワーカーのレジストリモデルが追加の配線なしで適用されます。

  • createStandalone() はプライベートなレジストリを構築します。ワーカーループで使うと、リクエストごとにすべてのフォントが再解析されます。代わりに、共有レジストリを備えた DocumentFactoryInterface を使用してください。
  • Document は、設計上そもそも使い捨てです。1 つのインスタンスを論理的に異なるドキュメント間で再利用すると、状態が漏れます。ドキュメントごとに create() を呼び出し、ガベージコレクションに回収させてください。
  • FontRegistryInterface::lock() を呼ぶと、register()addFontDirectory()warmup()LogicException をスローするようになります。ロックはウォームアップ後に行い、リクエスト処理中には決して行わないでください。
  • OutputDestination::File はサーバーのファイルシステムに書き込み、生のバイトを返します。save() は明示的なファイルパスを指定します。同じドキュメントに対してこの 2 つを混在させないでください。
  • cell() は、TCPDF 互換性のため border 引数に bool|string を受け付けます。空文字列は false と同じではありません。意図した型の値を渡してください。

フォントと画像のレジストリにより、ドキュメントドメインはリクエストごとのシステムではなく、メモリ制約型のシステムになります。支配的なのは初回リクエスト時のフォント解析です。performance_budget は、ワーカーの例で 3 つのドキュメントにわたり、実時間 1500 ms、ピーク 64 MB です。そのバジェットのほとんどは、初回のフォント解析です。ウォームアップ後、ドキュメントごとに契約に起因する処理は O(1)、つまりレジストリの参照とコンテキストの割り当てです。いずれのレジストリでも memoryUsage() は、リアルタイムのキャパシティプランニング向けに MemoryReport を返します。ResettableService::reset() は、持続的な負荷の下でピークメモリを抑制します。

ドキュメント契約には暗号関連のサーフェスはありませんが、運用上は 2 つのリスクが当てはまります。第 1 に、image() はパスまたは URL を受け付けます。信頼できない入力を扱うシナリオでは、ユーザー制御の URL を直接渡すのではなく、ExternalResourcePolicyInterface(セキュリティポリシーのページを参照)を通じてリモート取得を制限してください。第 2 に、writeHtml() は HTML パイプラインのエントリーポイントです。信頼できないマークアップは、レンダリング前に HtmlSecurityPolicyInterface を通過させる必要があります。ドキュメント層自体はサニタイズを行いません。それはセキュリティポリシードメインの役割であり、契約であるため、フォークすることなくより厳格なポリシーを提供できます。

ドキュメント契約は、ISO 32000-2 で定義されている PDF 2.0 のドキュメント構造を実装します。出力、ページ、フォントの処理は、ISO 32000-2 §7 に従って間接オブジェクトとクロスリファレンスストリームを生成します。コンテンツは、エンジン層の契約(ADR-010)に従ってライター層を通じて出力されます。このページでは、構造的な準拠を超える条項レベルの主張は行いません。PDF/A および PDF/UA への準拠は、規定の表を掲載している抽出ページとアクセシビリティページで説明しています。