跳到內容

在正式環境中使用 Artisan

在正式環境中,請注入已設定好的 renderer(渲染器)與 PSR-3 logger,讓同一個執行中的 Chrome 行程能跨多次繪製重複使用;為包含多個元素的文件提供明確高度,並在進入繪製路徑之前,用上游 timeout(逾時)設定界限。

BrowserPool 讓單一 Chrome 行程持續存活(keepAlive: true),並在每繪製 100 次後重啟一次,以限制記憶體成長——這是長壽命 CDP client 的已知累積模式。對於需要繪製大量文件的 worker,你會需要一個長壽命的 renderer,而不是每個請求各自建立一個,如此才能將 Chrome 的啟動成本攤到最低。

render-service.php
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
use NextPDF\Artisan\Exception\ChromeNotAvailableException;
use NextPDF\Artisan\Exception\ChromeRenderException;
use Psr\Log\LoggerInterface;
final class ReportRenderer
{
private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger)
{
$config = ChromeRendererConfig::fromArray([
'chrome_binary' => getenv('CHROME_BINARY') ?: null,
'render_timeout' => 45,
'max_html_size' => 2_000_000,
'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'),
]);
$this->renderer = new ChromeHtmlRenderer($config, $logger);
}
public function render(string $html, float $widthPt, float $heightPt = 0.0): string
{
try {
return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData();
} catch (ChromeNotAvailableException $e) {
// Deployment fault: Chrome runtime missing. Page the on-call owner.
throw $e;
} catch (ChromeRenderException $e) {
// Render-time fault: timeout, crash, empty output. Retryable once.
throw $e;
}
}
public function shutdown(): void
{
$this->renderer->close();
}
}

renderer 只需建構一次,之後即可重複使用。在 worker 關閉時呼叫 close(),能確定地釋放 Chrome 行程,而不必等待解構子(destructor)。這兩個 catch 分支會區分部署層面的錯誤(執行環境缺失)與繪製階段的錯誤(可重試)。不要使用空的 catch 區塊。

以單例(singleton)方式將它接入容器:

$container->singleton(ReportRenderer::class, fn ($c) =>
new ReportRenderer($c->get(Psr\Log\LoggerInterface::class)));

省略高度時,橋接會在 Chrome 中量測內容高度(取 body/document 捲動高度與位移高度的 max),轉換為點,並加上約 0.2 英吋(約 14.4 pt)的安全緩衝。這個緩衝用來吸收 Chrome 視區版面與列印版面回流之間的差異。若沒有它,printToPDF 可能溢出到第二頁,而 PageImporter(僅取第 0 頁)會把第二頁裁切掉。系統會強制套用 0.1 英吋的最小紙張高度。ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault::renderAutoFitBufferIsAddedNotSubtracted::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight 這幾個測試會驗證此行為。

對於固定版面的文件(發票、憑證),請以點為單位傳入明確高度。高度明確指定時,不會加上緩衝;輸出會精準符合要求的紙張尺寸(由 ::renderHonorsExplicitHeightWithoutAutoBuffer 驗證)。

  • 每個 worker 都建構一個 renderer,並重複使用。BrowserPool 會重複使用執行中的瀏覽器,並在每 100 次繪製的界線上自動重啟。
  • 若你想在 100 次繪製的界線之前取得全新的 Chrome 行程,請在 worker 關閉時以及在大型批次之間呼叫 close()
  • 解構子會呼叫 close(),但明確呼叫 close() 更具確定性,是長時間執行行程中的首選做法。
  • 重啟通知會以 notice 等級連同繪製次數一併記錄;重啟頻率升高時請發出警示,這代表文件比預期更重。

注入 PSR-3 logger。發出的事件與等級如下:

事件等級情境資料
繪製開始debugsize, width, height
繪製完成debugpdfSize, contentHeight
瀏覽器啟動infobinary
瀏覽器重啟noticecount
瀏覽器關閉debugrenderCount

不會記錄任何 HTML、PDF 位元組或擷取出的文字;這與 NIST SP 800-92 關於避免將酬載寫入維運日誌的指引一致。請用 start/complete 這組事件建立延遲 SLO,並用 notice 事件建立重啟頻率警示。

  • Sidecar Chrome:在與 PHP worker 相同的容器中執行 Chrome,並釘選 chrome_binary。佈建支援 sandbox 的容器;請參閱 /integrations/artisan/chrome-renderer-setup/。
  • 無容器/CLI:Artisan 沒有 DI 容器。在 CLI runner 中,請使用 EInvoiceServiceFactory 來建立 Premium 電子發票合約;請參閱 /integrations/artisan/boot-and-discovery/。
  • 資源界限:將 render_timeout 與上游請求預算,以及主機端的 cgroup/ulimit 搭配使用。請參閱 /integrations/artisan/security-and-operations/ 的威脅模型。
  • 若 renderer 在繪製途中被中斷,仍會關閉該 Chrome 頁面(finally),pool 仍可繼續使用。
  • 不支援跨 threads/processes 重複使用同一個 renderer;一個 renderer 擁有一個 Chrome 行程。
  • 100 次繪製後重啟這個門檻是固定的;安排批次大小時請將它納入考量,以取得可預期的延遲尖峰。

穩定狀態下的成本來自 Chrome 對輸入進行版面配置再加上 printToPDF,而不是橋接本身的額外開銷。啟動成本由 keepAlive 攤平。預期每第 100 次繪製(行程重啟)都會出現一次延遲尖峰;請把它呈現在 SLO 中,而不是當成事件處理。

正式環境路徑正是不可信任 HTML 的進入點。請重新詳閱 /integrations/artisan/security-and-operations/。不論如何設定,網路層屏障都仍然成立,但 no_sandbox: true 會移除 Chrome 的行程隔離,並提高對輸入的信任要求。

在無容器的 worker 中,未安裝 Premium 時 EInvoiceServiceFactory 會回傳 null,因此開源的繪製路徑會照常運作;安裝 Pro/Enterprise 即可在已繪製的文件上啟用電子發票嵌入與驗證。

  • 參閱 /integrations/artisan/quickstart/ 一節
  • 參閱 /integrations/artisan/configuration/ 一節
  • 參閱 /integrations/artisan/security-and-operations/ 一節
  • 參閱 /integrations/artisan/chrome-renderer-setup/ 一節
  • 參閱 /integrations/artisan/troubleshooting/ 一節