跳到內容

CodeIgniter 4 正式環境使用

正式環境中的控制器會依賴具體的 NextPDF 服務。這些控制器會明確處理已記載的例外階層,並發出可觀測性訊號。耗時的 PDF 工作會透過 CodeIgniter 4 Queue 移出請求流程之外執行。

CodeIgniter 4 會透過自身的定位器(locator)來 resolve(解析)此套件的服務。服務定位器(service locator)模式會把容器(container)交給某個物件,讓該物件自行取出所需的依賴。此模式並不建議使用(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) 每次呼叫都會回傳全新的函式庫與全新的底層文件。因此,並行請求之間絕不會共用文件狀態。此套件的功能測試會驗證這項行為。

字型與影像登錄表(registry)的設計,就是行程生命週期內的單例(singleton)。字型登錄表只會預熱並鎖定一次。影像登錄表是一個有界的最近最少使用(LRU)快取。在長駐的 worker(CodeIgniter spark 伺服器、RoadRunner 風格的執行器,或佇列 worker)中,這正是預期行為:昂貴的登錄表會持續保留,而每份文件都是全新的。請勿在請求或工作程式碼中索取共用文件(Services::pdfDocument(true));它只供測試重設使用,否則會在多個請求之間共用內容。

GeneratePdfJob 會透過 codeigniter4/queue 把 PDF 產生工作移出請求流程之外執行。佇列執行期會強制執行兩項事實,而這兩項你都必須正確設定。

佇列會以**名稱鍵(name key)**解析工作,而不是以類別字串解析。佇列處理器會比對推入的工作名稱與 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,
];
}

請以註冊的名稱作為第二個引數推入工作。第一個引數是佇列名稱。第三個引數是工作資料陣列。

<?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]);
}
}

此工作會把建構器(builder)可呼叫項限制在 App\PdfBuilders 命名空間(namespace),並把輸出路徑限定在 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() 從全新文件開始。它會套用建構器,再儲存到已驗證的路徑。此套件的測試會驗證連續兩次工作執行不會共用文件狀態。

  • 佇列在推入時會拒絕以 GeneratePdfJob::class 作為工作名稱,因為它不是已註冊的鍵 'generate-pdf'。請務必推入 jobHandlers 的鍵。
  • 建構器字串必須完全符合 App\PdfBuilders\<Class>::<method>。函式、其他命名空間,或帶有 prefixed/suffixed 的酬載,都會在任何程式碼執行之前引發 InvalidArgumentException
  • 輸出路徑必須解析到 WRITEPATH/pdfs/ 內部,並以 .pdf 結尾(不分大小寫)。路徑穿越與同層前綴的路徑都會被拒絕。
  • codeigniter4/queue 是此套件僅供開發使用的依賴。請在實際執行 worker 的應用程式中安裝它。

登錄表在每個 worker 行程中只會建立一次。文件建構成本會隨內容而變,與轉接器(adapter)無關。對大型批次工作,請優先採用佇列路徑,讓請求 worker 保持回應能力。在任何有可量測目標的 recipe(範例)中,都請設定 performance_budget

佇列工作是風險最高的介面。當 broker(訊息中介者)可被觸及時,佇列酬載可能受到攻擊者影響。其可呼叫項允許清單與路徑限定,連同已驗證的拒絕案例,記載於 /integrations/codeigniter/security-and-operations/ 一節。

  • 控制器收到的是具體服務,而非容器;這與 PSR-11 §1.3 的服務定位器指引一致。

NextPDF 核心採用 Apache-2.0 授權。在佇列工作中產生簽章與 PDF/A 輸出,需要在 worker 環境安裝 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/ —— 接線參考與煙霧測試。