ข้ามไปยังเนื้อหา

การใช้งาน Artisan ใน production

ในการใช้งานจริง ให้ inject renderer ที่กำหนดค่าไว้แล้วพร้อม logger แบบ PHP Standards Recommendation 3 (PSR-3) นำกระบวนการ Chrome ที่ทำงานอยู่กลับมาใช้ซ้ำระหว่างการเรนเดอร์ ระบุความสูงให้ชัดเจนสำหรับเอกสารที่มีหลายองค์ประกอบ และจำกัดเส้นทางการเรนเดอร์ด้วย timeout จากต้นทาง

BrowserPool รักษากระบวนการ Chrome หนึ่งกระบวนการให้ทำงานอยู่ (keepAlive: true) และรีสตาร์ตทุก 100 การเรนเดอร์เพื่อจำกัดการเติบโตของหน่วยความจำ ซึ่งเป็นรูปแบบการสะสมที่ทราบกันดีในไคลเอนต์ Chrome DevTools Protocol (CDP) ที่ทำงานเป็นเวลานาน สำหรับ worker ที่เรนเดอร์เอกสารจำนวนมาก ให้ใช้ renderer ที่ทำงานยาวนานหนึ่งตัวแทนการสร้าง 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 หนึ่งครั้งแล้วนำกลับมาใช้ซ้ำ เรียก close() เมื่อ worker ปิดตัวลงเพื่อปล่อยกระบวนการ Chrome อย่างคาดการณ์ได้ แทนการรอ destructor ส่วน catch ทั้งสองแยกข้อผิดพลาดของการ deploy (ขาด runtime) ออกจากข้อผิดพลาดขณะเรนเดอร์ (ลองใหม่ได้) อย่าใช้บล็อก catch ที่ว่างเปล่า

ผูกเข้ากับ container ในรูปแบบ singleton:

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

เมื่อละเว้นความสูง bridge จะวัดความสูงของเนื้อหาใน Chrome (ค่า max ของความสูง scroll และ offset ของ body/document) แปลงเป็นพอยต์ แล้วเพิ่ม safety buffer ประมาณ 0.2 นิ้ว (~14.4 pt) buffer นี้ครอบคลุมความต่างระหว่างเค้าโครงแบบ viewport ของ Chrome กับการ reflow ในเค้าโครงสำหรับการพิมพ์ หากไม่มี buffer นี้ printToPDF อาจดันเนื้อหาไปยังหน้าที่สอง ซึ่ง PageImporter (หน้า 0 เท่านั้น) จะตัดทิ้ง bridge บังคับให้ความสูงกระดาษขั้นต่ำอยู่ที่ 0.1 นิ้ว การทดสอบ ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault, ::renderAutoFitBufferIsAddedNotSubtracted และ ::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight ยืนยันพฤติกรรมนี้

สำหรับเอกสารที่มีเค้าโครงตายตัว (ใบแจ้งหนี้ ใบรับรอง) ให้ส่งความสูงเป็นพอยต์อย่างชัดเจน เมื่อระบุความสูงอย่างชัดเจน จะไม่มีการเพิ่ม buffer และผลลัพธ์จะตรงกับขนาดกระดาษที่ร้องขอพอดี (ยืนยันโดย ::renderHonorsExplicitHeightWithoutAutoBuffer)

  • สร้าง renderer หนึ่งตัวต่อ worker หนึ่งตัว แล้วนำกลับมาใช้ซ้ำ BrowserPool นำเบราว์เซอร์ที่ทำงานอยู่กลับมาใช้ซ้ำและรีสตาร์ตโดยอัตโนมัติเมื่อครบขอบเขต 100 การเรนเดอร์
  • เรียก close() เมื่อ worker ปิดตัว และระหว่าง batch ขนาดใหญ่เมื่อต้องการกระบวนการ Chrome ใหม่เร็วกว่าขอบเขต 100 การเรนเดอร์
  • destructor จะเรียก close() แต่การเรียก close() อย่างชัดเจนให้ผลที่คาดการณ์ได้และเหมาะกับกระบวนการที่ทำงานยาวนาน
  • ระบบจะบันทึกการแจ้งเตือนการรีสตาร์ตที่ระดับ notice พร้อมจำนวนการเรนเดอร์ ให้ตั้งการแจ้งเตือนเมื่ออัตราการรีสตาร์ตสูงขึ้น เพราะบ่งชี้ว่าเอกสารหนักกว่าที่คาดไว้

ให้ inject logger แบบ PSR-3 แล้ว renderer จะส่งเหตุการณ์และระดับต่อไปนี้:

เหตุการณ์ระดับบริบท
เริ่มการเรนเดอร์debugsize, width, height
การเรนเดอร์เสร็จสมบูรณ์debugpdfSize, contentHeight
เปิดเบราว์เซอร์infobinary
รีสตาร์ตเบราว์เซอร์noticecount
ปิดเบราว์เซอร์debugrenderCount

ไม่มีการบันทึก HTML ไบต์ของ PDF หรือข้อความที่สกัดออกมา วิธีนี้ช่วยกันไม่ให้ payload เข้าไปอยู่ใน log ของการดำเนินงาน และสอดคล้องกับแนวทางเนื้อหา log ตาม National Institute of Standards and Technology Special Publication (NIST SP) 800-92 สร้าง service-level objective (SLO) ของ latency จากคู่ start/complete และสร้างการแจ้งเตือนอัตราการรีสตาร์ตจากเหตุการณ์ notice ที่บันทึกไว้

  • Sidecar Chrome: เรียกใช้ Chrome ใน container เดียวกับ PHP worker ตรึงค่า chrome_binary และจัดเตรียม container ที่รองรับ sandbox ดูที่ /integrations/artisan/chrome-renderer-setup/
  • Containerless / CLI: Artisan ไม่มี dependency injection container ใช้ EInvoiceServiceFactory สำหรับสัญญา e-invoice ของ Premium ใน runner แบบ command-line interface (CLI) ดูที่ /integrations/artisan/boot-and-discovery/
  • Resource bounding: จับคู่ render_timeout กับงบประมาณคำขอจากต้นทางและ cgroup/ulimit ของโฮสต์ ดูแบบจำลองภัยคุกคามที่ /integrations/artisan/security-and-operations/
  • แม้ renderer จะถูกขัดจังหวะกลางการเรนเดอร์ ก็ยังคงปิดหน้า Chrome (finally) และ pool ยังคงใช้งานได้
  • ไม่รองรับการนำ renderer หนึ่งตัวกลับมาใช้ซ้ำข้าม threads/processes renderer หนึ่งตัวเป็นเจ้าของกระบวนการ Chrome หนึ่งกระบวนการ
  • การรีสตาร์ตเมื่อครบ 100 การเรนเดอร์เป็นค่าตายตัว ให้กำหนดขนาด batch โดยคำนึงถึงเรื่องนี้เพื่อให้การพุ่งขึ้นของ latency คาดการณ์ได้

ในสภาวะคงตัว ต้นทุนคือการจัดเค้าโครง input ของ Chrome บวกกับ printToPDF ไม่ใช่ overhead ของ bridge keepAlive เฉลี่ยต้นทุนการเริ่มต้นออกไปตลอดการเรนเดอร์ คาดว่าจะมีการพุ่งขึ้นของ latency ในการเรนเดอร์ทุกครั้งที่ 100 (การรีสตาร์ตกระบวนการ) ให้สะท้อนเรื่องนี้ไว้ใน SLO แทนที่จะถือว่าเป็นเหตุการณ์ผิดปกติ

เส้นทางการใช้งานจริงรับ HTML ที่ไม่น่าเชื่อถือ อ่าน /integrations/artisan/security-and-operations/ ซ้ำอีกครั้ง มาตรการกั้นเครือข่ายยังคงทำงานไม่ว่าจะกำหนดค่าอย่างไร แต่ no_sandbox: true จะลบการแยกกระบวนการของ Chrome ออกไปและเพิ่มข้อกำหนดด้านความน่าเชื่อถือต่อ input

ใน worker แบบ containerless EInvoiceServiceFactory จะคืนค่า null เมื่อยังไม่ได้ติดตั้ง Premium ดังนั้นเส้นทางการเรนเดอร์แบบโอเพนซอร์สจึงทำงานต่อไปโดยไม่เปลี่ยนแปลง ติดตั้ง Pro/Enterprise เพื่อเปิดใช้การฝังและการตรวจสอบ e-invoice บนเอกสารที่เรนเดอร์แล้ว

  • การเริ่มต้นใช้งาน Artisan (/integrations/artisan/quickstart/)
  • การกำหนดค่า Artisan (/integrations/artisan/configuration/)
  • ความปลอดภัยและการดำเนินงาน Artisan (/integrations/artisan/security-and-operations/)
  • การตั้งค่า Chrome renderer (/integrations/artisan/chrome-renderer-setup/)
  • การแก้ไขปัญหา Artisan (/integrations/artisan/troubleshooting/)