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

การใช้งานจริงใน CodeIgniter 4

คอนโทรลเลอร์สำหรับระบบจริงจะรับเซอร์วิส NextPDF เป็นอ็อบเจ็กต์รูปธรรม จัดการลำดับชั้นของ exception ที่บันทึกไว้อย่างชัดเจน ส่งสัญญาณเพื่อการสังเกตการณ์ และย้ายงาน Portable Document Format (PDF) ที่ใช้เวลานานออกจากคำขอผ่านคิวของ CodeIgniter 4

CodeIgniter 4 จะจัดเตรียมการอ้างอิงของเซอร์วิสในแพ็กเกจผ่านโลเคเตอร์ของตัวเอง ในรูปแบบ service-locator อ็อบเจ็กต์จะได้รับคอนเทนเนอร์ แล้วใช้คอนเทนเนอร์นั้นดึงการอ้างอิงของตัวเอง แนวทางของ PHP Standard Recommendation (PSR) ไม่สนับสนุนรูปแบบดังกล่าว (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
การป้องกันเส้นทาง / callableGeneratePdfJob โยน InvalidArgumentException

คอนโทรลเลอร์บนระบบจริง — การจัดการข้อผิดพลาดและความสามารถในการสังเกตการณ์

หัวข้อที่มีชื่อว่า “คอนโทรลเลอร์บนระบบจริง — การจัดการข้อผิดพลาดและความสามารถในการสังเกตการณ์”

เอนจินหลักจะโยน exception ที่สืบทอดจาก 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) ส่งคืนไลบรารีใหม่พร้อมเอกสารพื้นฐานใหม่ในทุกการเรียกใช้ คำขอที่ทำงานพร้อมกันจะไม่ใช้สถานะของเอกสารร่วมกันเลย การทดสอบเชิงฟังก์ชันของแพ็กเกจยืนยันพฤติกรรมนี้

อายุการใช้งานเซอร์วิสที่ปลอดภัยสำหรับเวิร์กเกอร์

หัวข้อที่มีชื่อว่า “อายุการใช้งานเซอร์วิสที่ปลอดภัยสำหรับเวิร์กเกอร์”

รีจิสทรีของฟอนต์และรูปภาพถูกออกแบบให้เป็นซิงเกิลตันตลอดอายุของโปรเซส รีจิสทรีของฟอนต์จะวอร์มและล็อกเพียงครั้งเดียว รีจิสทรีของรูปภาพเป็นแคชแบบ least recently used (LRU) ที่มีขนาดจำกัด ในเวิร์กเกอร์ที่มีอายุยืนยาว (CodeIgniter spark server, รันเนอร์สไตล์ RoadRunner หรือเวิร์กเกอร์คิว) พฤติกรรมนี้เป็นไปตามเจตนา รีจิสทรีที่มีต้นทุนสูงจะคงอยู่ ส่วนเอกสารทุกฉบับยังคงเป็นเอกสารใหม่ อย่าร้องขอเอกสารที่ใช้ร่วมกัน (Services::pdfDocument(true)) ในโค้ดของคำขอหรือโค้ดของงาน ตัวเลือกนี้มีไว้เพื่อรีเซ็ตในการทดสอบเท่านั้น และจะทำให้เนื้อหาถูกใช้ร่วมกันข้ามคำขอ

GeneratePdfJob ย้ายการสร้าง PDF ออกจากคำขอผ่าน codeigniter4/queue รันไทม์ของคิวต้องใช้การตั้งค่าสองรายการ กำหนดค่าทั้งสองรายการให้ถูกต้อง

คิวจะจัดเตรียมงานด้วย คีย์ชื่อ ไม่ใช่ด้วยสตริงคลาส ตัวจัดการคิวจะตรวจสอบชื่องานที่ส่งเข้ามาเทียบกับคีย์ของ 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]);
}
}

งานนี้อนุญาตให้ใช้ callable ของบิลเดอร์เฉพาะในเนมสเปซ App\PdfBuilders เท่านั้น และจำกัดเส้นทางเอาต์พุตไว้ที่ 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> ทุกประการ ฟังก์ชัน เนมสเปซอื่น หรือเพย์โหลดที่มีคำนำหน้าหรือคำต่อท้ายจะทำให้เกิด InvalidArgumentException ก่อนที่จะมีโค้ดใดทำงาน
  • เส้นทางเอาต์พุตเมื่อจัดเตรียมแล้วต้องอยู่ภายใน WRITEPATH/pdfs/ และลงท้ายด้วย .pdf (ไม่คำนึงถึงตัวพิมพ์ใหญ่เล็ก) เส้นทางแบบ traversal และเส้นทางที่มีคำนำหน้าระดับเดียวกันจะถูกปฏิเสธ
  • codeigniter4/queue เป็นการอ้างอิงสำหรับการพัฒนาเท่านั้นของแพ็กเกจ ให้ require การอ้างอิงนี้ในแอปพลิเคชันที่รันเวิร์กเกอร์

รีจิสทรีจะถูกสร้างเพียงครั้งเดียวต่อหนึ่งโปรเซสของเวิร์กเกอร์ ต้นทุนการสร้างเอกสารขึ้นอยู่กับเนื้อหา ไม่ใช่อะแดปเตอร์ สำหรับงานแบบกลุ่มขนาดใหญ่ ให้ใช้เส้นทางผ่านคิวเพื่อให้เวิร์กเกอร์ที่รับคำขอยังคงตอบสนองได้ กำหนด performance_budget ในเรซิพีใด ๆ ที่มีเป้าหมายวัดผลได้

งานคิวเป็นพื้นผิวที่มีความเสี่ยงสูงที่สุด เมื่อเข้าถึงโบรกเกอร์ได้ ให้ถือว่าเพย์โหลดของคิวอยู่ภายใต้อิทธิพลของผู้โจมตี allowlist ของ callable และการจำกัดเส้นทางอธิบายไว้ใน /integrations/codeigniter/security-and-operations/ พร้อมกับกรณีการปฏิเสธที่ได้รับการตรวจสอบแล้ว

  • คอนโทรลเลอร์จะรับเซอร์วิสที่เป็นอ็อบเจ็กต์รูปธรรม ไม่ใช่คอนเทนเนอร์ ซึ่งสอดคล้องกับแนวทางเกี่ยวกับ service-locator ของ PSR-11 §1.3

แกนหลักของ NextPDF อยู่ภายใต้ Apache-2.0 หากต้องการสร้างเอาต์พุตที่มีการลงนามและ PDF/A ในงานคิว ให้ติดตั้ง NextPDF Pro หรือ Enterprise ในสภาพแวดล้อมของเวิร์กเกอร์ แพ็กเกจ CodeIgniter เปิดเผยเมท็อดเซอร์วิสที่เกี่ยวข้อง เมท็อดเหล่านี้ส่งคืน null จนกว่าจะติดตั้งแพ็กเกจ Premium ที่ตรงกัน ดู </get-license/?intent=codeigniter-async-signing>

  • /integrations/codeigniter/quickstart/ — เวอร์ชันขั้นต่ำของคอนโทรลเลอร์เหล่านี้
  • /integrations/codeigniter/configuration/ — การลงนามด้วย Time Stamping Authority (TSA) และการกำหนดค่าเส้นทาง
  • /integrations/codeigniter/security-and-operations/ — โมเดลภัยคุกคามของคิวและการเสริมความแข็งแกร่ง
  • /integrations/codeigniter/troubleshooting/ — รูปแบบความล้มเหลวของคิวและการค้นพบ
  • /integrations/codeigniter/integration/ — ข้อมูลอ้างอิงการเชื่อมต่อและการทดสอบเบื้องต้น