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

การใช้แพ็กเกจ NextPDF Laravel ในสภาพแวดล้อมจริง

สำหรับการใช้งานจริง ให้ resolve สัญญา (contract) ของเอกสารผ่าน constructor injection จัดการความล้มเหลวในการเขียน PDF ด้วย exception ที่เฉพาะเจาะจง ย้ายงานสร้างที่หนักหรือเป็นชุด (batch) ไปยัง GeneratePdfJob และเชื่อมต่อ callback สำหรับกรณีสำเร็จและกรณีล้มเหลวให้ชัดเจน

Terminal window
composer require nextpdf/laravel
php 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 พร้อมการจัดการข้อผิดพลาดแบบระบุชนิด”
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?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 และต้องคืนเอกสารที่กำหนดค่าแล้ว

resource: src/Laravel/Jobs/GeneratePdfJob.php
<?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 ได้ เพื่อให้คงอยู่ตลอดการขนส่งผ่านคิว

พร็อพเพอร์ตีค่าเริ่มต้นคีย์การกำหนดค่า
tries3ไม่ได้ควบคุมผ่านการกำหนดค่า ให้สร้าง subclass เพื่อเปลี่ยนแปลง
timeout120nextpdf.queue.timeout
backoff10ไม่ได้ควบคุมผ่านการกำหนดค่า ให้สร้าง subclass เพื่อเปลี่ยนแปลง
ชื่อคิวpdfnextpdf.queue.queue
การเชื่อมต่อdefaultnextpdf.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/ — ความล้มเหลวที่พบบ่อยในการใช้งานจริง