コンテンツにスキップ

CodeIgniter 4 での本番利用

本番コントローラーは、具体的な NextPDF サービスに依存します。これらのコントローラーは、ドキュメント化された例外階層を明示的に処理し、可観測性シグナルを出力します。時間のかかる PDF 処理は、CodeIgniter 4 Queue を通じてリクエストの外へ移します。

CodeIgniter 4 は、ロケーターを通じてパッケージのサービスを resolve(解決)します。サービスロケーターパターンでは、オブジェクトが自身の依存関係を取得できるよう、コンテナーをオブジェクトに渡します。このパターンは推奨されません(PSR-11 §1.3、modal SHOULD NOT)。このガイダンスに従うには、各 NextPDF サービスをコントローラーの境界で一度だけ解決し、具体的なオブジェクトとして内側に渡します。Services クラス(またはコンテナー)をドメインコードに渡さないでください。

すべての PHP サンプルでは、declare(strict_types=1); を独立した行で宣言します(PSR-12 §x1.x3.p34)。

本番での関心事検証済みのサーフェス
サービスの解決Services::pdf(false), Services::pdfDocument(false), Services::documentFactory()
レスポンスの構築PdfResponse::download() / inline()DownloadResponse
失敗の捕捉NextPDF\Exception\NextPdfException(エコシステムの基底型)
非同期生成GeneratePdfJob を登録する Config\Queue::$jobHandlers
パス/呼び出し可能オブジェクトのガードGeneratePdfJob がスローする InvalidArgumentException

コアエンジンがスローする例外は、すべて NextPDF\Exception\NextPdfException を継承しています。この単一の型をキャッチすれば、コアと拡張の両方の失敗をカバーできます。ここでの catch ブロックは、コンテキスト付きでログを記録し、定義済みのエラーレスポンスを返します。空の catch にはしません。

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\ResponseInterface;
use NextPDF\CodeIgniter\Config\Services;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final class InvoiceController extends BaseController
{
public function download(int $id): DownloadResponse|ResponseInterface
{
/** @var LoggerInterface $logger */
$logger = \service('logger');
$start = \hrtime(true);
try {
$pdf = Services::pdf(false);
$pdf->document()->addPage();
$pdf->document()->cell(0, 10, "Invoice #{$id}");
$response = $pdf->download("invoice-{$id}.pdf");
$logger->info('pdf.invoice.generated', [
'invoice_id' => $id,
'elapsed_ms' => (\hrtime(true) - $start) / 1_000_000,
]);
return $response;
} catch (NextPdfException $e) {
$logger->error('pdf.invoice.failed', [
'invoice_id' => $id,
'exception' => $e::class,
'message' => $e->getMessage(),
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_INTERNAL_SERVER_ERROR)
->setJSON(['error' => 'pdf_generation_failed', 'invoice_id' => $id]);
}
}
}

Services::pdf(false) は、呼び出しごとに新しいライブラリと新しい基底ドキュメントを返します。そのため、並行するリクエスト間でドキュメントの状態が共有されることはありません。パッケージの機能テストでこの動作を検証しています。

フォントレジストリと画像レジストリは、設計上、プロセスのライフタイム全体で維持されるシングルトンです。フォントレジストリは一度だけウォームアップされ、ロックされます。画像レジストリは、容量制限付きの LRU(最長未使用)キャッシュです。長時間稼働するワーカー(CodeIgniter spark サーバー、RoadRunner 型のランナー、またはキューワーカー)では、これは意図された動作です。コストの高いレジストリは保持され、各ドキュメントは新規に作成されます。リクエストコードやジョブコードで共有ドキュメント(Services::pdfDocument(true))を要求しないでください。これはテストのリセット専用で、リクエスト間でコンテンツを共有してしまいます。

GeneratePdfJob は、codeigniter4/queue を通じてリクエストの外で PDF 生成を実行します。キューランタイムでは 2 つの条件が強制されるため、その両方を正しく設定する必要があります。

キューは、クラス文字列ではなく 名前キー によってジョブを解決します。キューハンドラーは、プッシュされたジョブ名を Config\Queue::$jobHandlers のキーと照合して検証します。未知の名前は CodeIgniter\Queue\Exceptions\QueueException で拒否されます。ジョブを app/Config/Queue.php に登録します。

<?php
declare(strict_types=1);
namespace Config;
use CodeIgniter\Queue\Config\Queue as BaseQueue;
use NextPDF\CodeIgniter\Jobs\GeneratePdfJob;
final class Queue extends BaseQueue
{
/** @var array<string, class-string> */
public array $jobHandlers = [
'generate-pdf' => GeneratePdfJob::class,
];
}

登録した名前を第 2 引数に指定して、ジョブをプッシュします。第 1 引数はキュー名です。第 3 引数はジョブデータの配列です。

<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
final class InvoiceController extends BaseController
{
public function queueInvoice(int $id): ResponseInterface
{
\service('queue')->push('pdf-queue', 'generate-pdf', [
'builder' => 'App\\PdfBuilders\\InvoiceBuilder::build',
'outputPath' => WRITEPATH . 'pdfs/invoice-' . $id . '.pdf',
'context' => ['invoice_id' => $id],
]);
return $this->response
->setStatusCode(ResponseInterface::HTTP_ACCEPTED)
->setJSON(['status' => 'queued', 'invoice_id' => $id]);
}
}

このジョブは、ビルダーの呼び出し可能オブジェクトを App\PdfBuilders 名前空間に制限し、出力パスを WRITEPATH/pdfs/ 内に限定します。ビルダーは静的メソッドです。このメソッドは新しい Document とコンテキスト配列を受け取り、ドキュメントを返します。

<?php
declare(strict_types=1);
namespace App\PdfBuilders;
use NextPDF\Core\Document;
final class InvoiceBuilder
{
/** @param array<string, mixed> $context */
public static function build(Document $document, array $context): Document
{
$invoiceId = (int) ($context['invoice_id'] ?? 0);
$document->addPage();
$document->cell(0, 10, "Invoice #{$invoiceId}");
return $document;
}
}
Terminal window
php spark queue:work pdf-queue

各ジョブの実行は、Services::pdfDocument() を介して新しいドキュメントから開始されます。ビルダーを適用した後、検証済みのパスに保存します。パッケージのテストでは、連続する 2 回のジョブ実行でドキュメントの状態が共有されないことを検証しています。

  • キューは、プッシュ時にジョブ名として GeneratePdfJob::class を拒否します。これは登録済みのキー 'generate-pdf' ではないためです。常に jobHandlers のキーをプッシュしてください。
  • ビルダー文字列は App\PdfBuilders\<Class>::<method> と完全に一致する必要があります。関数、別の名前空間、または prefixed/suffixed なペイロードは、コードが実行される前に InvalidArgumentException を発生させます。
  • 出力パスは WRITEPATH/pdfs/ 内に解決され、.pdf で終わる必要があります(大文字・小文字は区別されません)。トラバーサルパスや兄弟プレフィックスのパスは拒否されます。
  • codeigniter4/queue は、パッケージの開発専用の依存関係です。ワーカーを実行するアプリケーションでこれを require してください。

レジストリは、ワーカープロセスごとに一度だけ作成されます。ドキュメントの構築コストは、アダプターではなくコンテンツに比例して増加します。大規模なバッチジョブでは、リクエストワーカーの応答性を保つために、キュー経由の処理を優先してください。測定可能な目標があるレシピには、performance_budget を設定してください。

キュージョブは、最もリスクの高いサーフェスです。ブローカーに到達できる場合、キューのペイロードは攻撃者の影響を受けます。呼び出し可能オブジェクトの許可リストとパスの限定については、検証済みの拒否ケースとともに /integrations/codeigniter/security-and-operations/ で説明しています。

  • コントローラーは、PSR-11 §1.3 のサービスロケーターに関するガイダンスに従い、コンテナーではなく具体的なサービスを受け取ります。

NextPDF コアは Apache-2.0 です。キュージョブで署名付き出力や PDF/A 出力を行うには、ワーカー環境に NextPDF Pro または Enterprise がインストールされている必要があります。CodeIgniter パッケージは、対応するサービスメソッドを公開します。これらのメソッドは、対応する Premium パッケージがインストールされるまで null を返します。</get-license/?intent=codeigniter-async-signing> を参照してください。

  • /integrations/codeigniter/quickstart/ — これらのコントローラーの最小構成版です。
  • /integrations/codeigniter/configuration/ — 署名、TSA、およびパスの設定です。
  • /integrations/codeigniter/security-and-operations/ — キューの脅威モデルと堅牢化です。
  • /integrations/codeigniter/troubleshooting/ — キューとディスカバリーの障害モードです。
  • /integrations/codeigniter/integration/ — 配線リファレンスとスモークテストです。