การใช้แพ็กเกจ NextPDF Laravel ในสภาพแวดล้อมจริง
ภาพรวมโดยย่อ
หัวข้อที่มีชื่อว่า “ภาพรวมโดยย่อ”สำหรับการใช้งานจริง ให้ resolve สัญญา (contract) ของเอกสารผ่าน constructor injection จัดการความล้มเหลวในการเขียน PDF ด้วย exception ที่เฉพาะเจาะจง ย้ายงานสร้างที่หนักหรือเป็นชุด (batch) ไปยัง GeneratePdfJob และเชื่อมต่อ callback สำหรับกรณีสำเร็จและกรณีล้มเหลวให้ชัดเจน
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/laravelphp artisan vendor:publish --tag=nextpdf-configกำหนดค่าการเชื่อมต่อคิวใน config/nextpdf.php ตั้งค่า queue.connection, queue.queue และ queue.timeout จากนั้นตรวจสอบให้แน่ใจว่ามี worker ทำงานอยู่บนการเชื่อมต่อที่กำหนดค่าไว้
ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”container เปิดให้เข้าถึง NextPDF\Contracts\PdfDocumentInterface เป็น factory binding การ resolve แต่ละครั้งจะให้ NextPDF\Core\Document ใหม่ PSR-11 อนุญาตให้ container คืนค่าที่แตกต่างกันเมื่อเรียก get() ต่อเนื่องกัน ขึ้นอยู่กับกลยุทธ์การ binding (PSR-11 §1.1.2) แพ็กเกจนี้ใช้ factory binding เพื่อให้สถานะที่เปลี่ยนแปลงได้ในขอบเขตของ request ไม่ข้ามไปยัง request อื่น รีจิสทรีของฟอนต์และรูปภาพเป็น singleton แนวทางนี้รักษาสัญญา (contract) ที่ว่า identifier ที่ถูก bind ไว้จะ resolve ไปยัง entry ที่ลงทะเบียนไว้ (PSR-11 §1.1.2) ในขณะที่ยังแชร์ทรัพยากรที่มีต้นทุนสูงร่วมกันทั่วทั้ง worker
ในโค้ดที่ใช้งานจริง ควรใช้ constructor injection มากกว่า facade วิธีนี้ทำให้ dependency ชัดเจนขึ้น และทำให้ controller สามารถทำ unit test ได้โดยไม่ต้อง boot facade root
ตัวอย่างโค้ด — การใช้งานจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — การใช้งานจริง”controller ที่เชื่อมต่อผ่าน DI พร้อมการจัดการข้อผิดพลาดแบบระบุชนิด
หัวข้อที่มีชื่อว่า “controller ที่เชื่อมต่อผ่าน DI พร้อมการจัดการข้อผิดพลาดแบบระบุชนิด”<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly PdfDocumentInterface $document, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $this->document->addPage(); $this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true); $this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download( $this->document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Rethrow as an HTTP-meaningful failure; never swallow. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}ให้ inject PdfDocumentInterface ไม่ใช่คลาสรูปธรรม Document เพื่อให้สลับ binding สำหรับการทดสอบได้ container จะคืนเอกสารใหม่เมื่อสร้าง instance ของ controller แต่ละครั้ง อย่านำ instance ของ controller เดียวกันกลับมาใช้ซ้ำกับเอกสารสองรายการที่ไม่เกี่ยวข้องกันภายในกระบวนการเดียว
บล็อก catch จะบันทึกคลาสของ exception และคืนค่าข้อผิดพลาด HTTP ที่กำหนดไว้ แทนการเปิดเผย stack trace ใช้ Psr\Log\LoggerInterface ซึ่ง container จะ resolve ไปยัง logger ของเฟรมเวิร์ก PSR-3 มอบหน้าที่การ escape placeholder ให้กับผู้พัฒนา (implementor) และระบุให้ผู้เรียกไม่ต้อง escape ค่า context ไว้ล่วงหน้า (PSR-3 §1.2) ส่ง context แบบมีโครงสร้าง ไม่ใช่สตริงที่แทรกค่าเข้าไป (interpolated)
การสร้างผ่านคิวพร้อม callback สำหรับกรณีสำเร็จและกรณีล้มเหลว
หัวข้อที่มีชื่อว่า “การสร้างผ่านคิวพร้อม callback สำหรับกรณีสำเร็จและกรณีล้มเหลว”GeneratePdfJob เป็น job แบบ ShouldQueue โดยค่าเริ่มต้นจะพยายามสามครั้ง มี timeout 120 วินาที และ backoff 10 วินาที คุณสามารถ override ค่าทั้งสามได้ใน config/nextpdf.php builder closure จะรับเอกสารที่ resolve จาก container และต้องคืนเอกสารที่กำหนดค่าแล้ว
<?php
declare(strict_types=1);
namespace App\Jobs;
use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Jobs\GeneratePdfJob;use Psr\Log\LoggerInterface;use Throwable;
final class DispatchMonthlyStatement{ public function __construct(private readonly LoggerInterface $logger) {}
public function __invoke(int $accountId): void { // Dispatchable::dispatch() is `public static`: it constructs the // job from the arguments it receives and returns a PendingDispatch. // Pass every constructor argument — including the callbacks — to // the static call. Building an instance and then calling // `$job->dispatch(...)` would discard that instance (and its // callbacks) and queue a different job from only the static args. GeneratePdfJob::dispatch( storage_path("app/statements/{$accountId}.pdf"), static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document ->addPage() ->cell(0, 10, "Statement for account {$accountId}", newLine: true), function (string $path) use ($accountId): void { $this->logger->info('Statement PDF written', [ 'account_id' => $accountId, 'path' => $path, ]); }, function (Throwable $exception) use ($accountId): void { $this->logger->error('Statement PDF failed', [ 'account_id' => $accountId, 'exception' => $exception::class, ]); }, ); }}GeneratePdfJob::dispatch() จะส่งต่ออาร์กิวเมนต์ไปยัง constructor (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure) โดยตรง ส่งผลให้ callback สำหรับกรณีสำเร็จและกรณีล้มเหลวเชื่อมอยู่กับ job เดียวกันที่ถูกนำเข้าคิว ซึ่งสอดคล้องกับรูปแบบ positional GeneratePdfJob::dispatch($path, $builder) ใน /integrations/laravel/quickstart/ callback สำหรับกรณีสำเร็จจะรับ output path ส่วน callback สำหรับกรณีล้มเหลวจะรับ Throwable นอกจากนี้ job ยังมี setter แบบ fluent เช่น then() และ catch() ซึ่งคืนค่า job เพื่อนำไปต่อแบบ chaining ได้ ใช้ setter เหล่านี้เฉพาะเมื่อคุณเก็บและ dispatch instance เดียวกัน เช่น ผ่าน helper dispatch() job ยังมีเมท็อด failed() ซึ่ง queue runner จะเรียกใช้เมื่อเกิดความล้มเหลวขั้นสุดท้าย callback จะถูกห่อไว้ใน closure ที่ serialize ได้ เพื่อให้คงอยู่ตลอดการขนส่งผ่านคิว
การปรับแต่งพฤติกรรมของคิว
หัวข้อที่มีชื่อว่า “การปรับแต่งพฤติกรรมของคิว”| พร็อพเพอร์ตี | ค่าเริ่มต้น | คีย์การกำหนดค่า |
|---|---|---|
tries | 3 | ไม่ได้ควบคุมผ่านการกำหนดค่า ให้สร้าง subclass เพื่อเปลี่ยนแปลง |
timeout | 120 | nextpdf.queue.timeout |
backoff | 10 | ไม่ได้ควบคุมผ่านการกำหนดค่า ให้สร้าง subclass เพื่อเปลี่ยนแปลง |
| ชื่อคิว | pdf | nextpdf.queue.queue |
| การเชื่อมต่อ | default | nextpdf.queue.connection |
tries และ backoff เป็น public property ที่อ่านจาก instance ของ job โดย job ที่มาพร้อมแพ็กเกจไม่ได้อ่านค่าเหล่านี้จากการกำหนดค่า หากนโยบายการลองใหม่ของคุณแตกต่างออกไป ให้สร้าง subclass ของ GeneratePdfJob เพื่อ override ค่าเหล่านั้น
กรณีขอบและข้อควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบและข้อควรระวัง”- builder closure ต้องคืนค่า
PdfDocumentInterfaceโดย job จะบันทึกค่าที่คืนกลับมานั้น ไม่ใช่ instance ที่ resolve มาในตอนแรก การทดสอบ job ยืนยันสัญญา (contract) นี้อย่างชัดเจน - การ resolve
SignerInterfaceจะคืนค่าnullเว้นแต่จะเปิดใช้งานการลงนาม กำหนดค่าใบรับรองไว้ และติดตั้งnextpdf/premiumแล้ว ตรวจสอบค่า null เสมอก่อนลงนาม - worker ที่ทำงานยาวนาน (Octane/RoadRunner/Swoole) จะแชร์รีจิสทรีฟอนต์ที่ถูกล็อกไว้ร่วมกัน กำหนดค่า
preload_fontsเพื่อให้การ warmup ทำงานเพียงครั้งเดียวตอน worker boot แทนที่จะเกิดขึ้นตอน request แรก - job ที่ล้มเหลวจะเรียกใช้
failed()หลังจากใช้triesจนครบ ความล้มเหลวในแต่ละครั้งจะไม่เรียกonFailureจนกว่า queue runner จะประกาศความล้มเหลวขั้นสุดท้าย
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”การสร้างแบบ synchronous ใน controller จะบล็อก request ตลอดช่วงที่สร้าง PDF ทั้งหมด สำหรับผลลัพธ์หลายหน้าหรือแบบชุด (batch) ให้ dispatch GeneratePdfJob แล้วคืนค่าทันที รีจิสทรีแบบ singleton จะกระจายต้นทุนการแยกวิเคราะห์ฟอนต์และการถอดรหัสรูปภาพไปตลอดอายุการทำงานของ worker ต้นทุนต่อ request จึงจำกัดอยู่ที่การสร้างเอกสารและการสร้างเนื้อหาเท่านั้น
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”controller ที่ใช้ dependency injection จะบันทึกคลาสของ exception ไม่ใช่ข้อความหรือ trace เพื่อหลีกเลี่ยงการเปิดเผยรายละเอียดภายในลงในบันทึก GeneratePdfJob จะตรวจสอบความถูกต้องของ output path บน worker เพื่อลดความเสี่ยงจาก payload ที่ถูก serialize แล้วถูกแก้ไขระหว่างการขนส่งผ่านคิว รายละเอียดทั้งหมดอยู่ใน /integrations/laravel/security-and-operations/
ความสอดคล้องตามมาตรฐาน
หัวข้อที่มีชื่อว่า “ความสอดคล้องตามมาตรฐาน”| ข้อกล่าวอ้าง | แหล่งที่มา | ข้อกำหนด | reference_id (ตัวระบุอ้างอิง) |
|---|---|---|---|
| identifier ที่ถูก bind ไว้จะ resolve ไปยัง entry ที่ลงทะเบียนไว้ | PSR-11 Container | §1.1.2 | |
| การ resolve ที่ต่อเนื่องกันอาจแตกต่างกันตามกลยุทธ์การ binding (factory binding) | PSR-11 Container | §1.1.2 |
แนวทางการบันทึก (logging) ของ PSR-3 อยู่ในข้อกำหนด PSR-3 แนวทางนี้มอบหน้าที่การ escape placeholder ให้กับผู้พัฒนา (implementor) และกำหนดให้ผู้เรียกส่ง context แบบมีโครงสร้าง ดูเอกสาร psr_3_logger §1.2
บริบทเชิงพาณิชย์
หัวข้อที่มีชื่อว่า “บริบทเชิงพาณิชย์”ผลลัพธ์ PAdES B-B ที่ลงนามแล้วและการเก็บถาวรแบบ PDF/A ผ่าน nextpdf/premium ใช้จุดเชื่อมต่อ dependency injection (DI) เดียวกัน นี่เป็นความสามารถระดับ Enterprise ที่เป็นทางเลือก แพ็กเกจ Core ที่อธิบายไว้ในเอกสารนี้ไม่ต้องเปลี่ยนโค้ดใด ๆ เพื่อนำไปใช้ ดู https://nextpdf.dev/get-license/?intent=laravel-signing
ดูเพิ่มเติม
หัวข้อที่มีชื่อว่า “ดูเพิ่มเติม”- /integrations/laravel/quickstart/ — ตัวอย่างแรกแบบย่อ
- /integrations/laravel/configuration/ — คีย์สำหรับคิว ลายเซ็น และฟอนต์
- /integrations/laravel/security-and-operations/ — แบบจำลองภัยคุกคามและการเสริมความแข็งแกร่ง
- /integrations/laravel/troubleshooting/ — ความล้มเหลวที่พบบ่อยในการใช้งานจริง