คู่มือนักพัฒนา Symfony
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”แพ็กเกจ Symfony ยึดเซอร์วิสเป็นหลัก ให้ฉีด PdfFactory เรียก create() สำหรับเอกสาร Portable Document Format (PDF) แต่ละฉบับ และใช้ Messenger builder สำหรับการสร้างแบบอะซิงโครนัส คุณสามารถเก็บ factory ไว้เป็นเซอร์วิสในคอนเทนเนอร์ได้ เพราะการเรียกแต่ละครั้งจะคืนเอกสารฉบับใหม่
ใช้คู่มือนี้สำหรับ nextpdf/symfony เมื่อออกแบบ controller, เซอร์วิส, Messenger handler หรือจุดขยาย (extension point) ระดับ bundle
ขอบเขตทางสถาปัตยกรรม
หัวข้อที่มีชื่อว่า “ขอบเขตทางสถาปัตยกรรม”| เลเยอร์ | เป็นเจ้าของโดย | ความรับผิดชอบ | สิ่งที่ไม่ควรใส่ที่นี่ |
|---|---|---|---|
| Controller | แอปพลิเคชัน | ตรวจสอบสิทธิ์คำขอ รวบรวมข้อมูลนำเข้า และคืนค่า PdfResponse | เค้าโครง PDF ที่ใช้ร่วมกันในหลายกรณีการใช้งาน |
| เซอร์วิสของแอปพลิเคชัน | แอปพลิเคชัน | โหลดข้อมูลโดเมนและเลือก builder | ตรรกะของ Symfony container compiler |
| เซอร์วิส builder | แอปพลิเคชัน | ใช้งาน PdfBuilderInterface เพื่อสร้างเอกสารแบบซิงโครนัสหรือแบบเข้าคิว | ออบเจกต์คำขอ, entity manager หรือบริบทที่ไม่สามารถซีเรียลไลซ์ได้ |
| Symfony bundle | nextpdf/symfony | ลงทะเบียนเซอร์วิส ผัง config, extension pass ที่เลือกได้, response helper และ Messenger data transfer object (DTO) | นโยบายการจัดเก็บเฉพาะผู้เช่า (tenant) |
| เอนจินหลัก | nextpdf/nextpdf | สร้างและซีเรียลไลซ์เอกสาร | พฤติกรรมของ Symfony response หรือ Messenger |
วงจรชีวิตขณะรันไทม์
หัวข้อที่มีชื่อว่า “วงจรชีวิตขณะรันไทม์”| ขั้นตอน | พฤติกรรม | การดำเนินการของนักพัฒนา |
|---|---|---|
| การบูต bundle | NextPdfBundle::build() ลงทะเบียนการตรวจหา extension ที่เลือกได้ | ให้ Symfony ค้นพบ bundle เอง หรือจะลงทะเบียน bundle ไว้ใน bundles.php ก็ได้ |
| การโหลด config | NextPdfExtension::load() ประมวลผล config ของ nextpdf: และโหลดนิยามเซอร์วิส | กำหนดค่าให้ชัดเจนและสอดคล้องกับสภาพแวดล้อม |
| การใช้ factory | PdfFactory::create() คืนเอกสารฉบับใหม่ที่กำหนดค่าแล้ว | อย่าเก็บเอกสารไว้ในเซอร์วิส |
| เอาต์พุตของ controller | PdfResponse แปลงเอกสารที่สร้างเสร็จให้เป็น response | ใช้ helper แทนการประกอบ header ด้วยตนเอง |
| การส่งงานผ่าน Messenger | GeneratePdfMessage บรรจุคลาส builder, พาธเอาต์พุต และบริบทที่ซีเรียลไลซ์ได้ | เก็บบริบทให้เล็กที่สุดและใช้ค่าสเกลาร์เป็นหลัก |
| การจัดการข้อความ | GeneratePdfHandler ค้นหา builder ผ่าน service locator และบันทึกเอกสาร | ทำให้ builder มีผลลัพธ์ที่กำหนดได้แน่นอน (deterministic) และทำซ้ำได้โดยไม่เปลี่ยนผลลัพธ์ (idempotent) |
โครงสร้างแอปพลิเคชันที่แนะนำ
หัวข้อที่มีชื่อว่า “โครงสร้างแอปพลิเคชันที่แนะนำ”| พาธ | วัตถุประสงค์ |
|---|---|
src/Pdf/Builder/* | เซอร์วิสที่ใช้งานอินเทอร์เฟซ PdfBuilderInterface |
src/Pdf/Data/* | DTO ขนาดเล็กหรืออาร์เรย์สำหรับใช้เป็นบริบทของ builder |
src/Pdf/Storage/* | การเลือกรากที่จัดเก็บ (storage root) และนโยบายชื่อไฟล์เอาต์พุต |
src/Controller/* | จุดเข้าสำหรับ response แบบซิงโครนัส |
tests/Pdf/* | การทดสอบ builder, response, Messenger และ config ต่างๆ |
ควรใช้เซอร์วิส builder มากกว่าฟังก์ชัน helper แบบสแตติก เซอร์วิสเหล่านี้ติดแท็ก ตกแต่ง (decorate) ทดสอบ และเรียกใช้จาก Messenger ได้ง่ายกว่า
<?php
namespace App\Pdf\Builder;
use NextPDF\Core\Document;use NextPDF\Symfony\Message\PdfBuilderInterface;
final readonly class InvoicePdfBuilder implements PdfBuilderInterface{ public function build(Document $document, array $context): Document { $document->setTitle((string) $context['title']) ->addPage() ->writeHtml((string) $context['html']);
return $document; }}รูปแบบ response แบบซิงโครนัส
หัวข้อที่มีชื่อว่า “รูปแบบ response แบบซิงโครนัส”<?php
namespace App\Controller;
use App\Pdf\Builder\InvoicePdfBuilder;use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;
final readonly class InvoiceController{ public function __invoke( PdfFactory $factory, InvoicePdfBuilder $builder, ) { $document = $builder->build($factory->create(), [ 'title' => 'Invoice 1234', 'html' => '<h1>Invoice 1234</h1>', ]);
return PdfResponse::download($document, 'invoice-1234.pdf'); }}เก็บบริบทใน controller ให้เล็ก เมื่อ builder ต้องการออบเจกต์โดเมนจำนวนมาก ให้ย้ายการประสานงาน (orchestration) ไปไว้ในเซอร์วิสของแอปพลิเคชัน แล้วส่ง DTO หรืออาร์เรย์ที่ปรับให้เป็นมาตรฐานแล้วไปยัง builder
รูปแบบ Messenger
หัวข้อที่มีชื่อว่า “รูปแบบ Messenger”GeneratePdfMessage ตรวจสอบความถูกต้องของคลาส builder และพาธเอาต์พุตก่อนส่งงาน handler จะตรวจสอบพาธอีกครั้งขณะทำงาน
<?php
use App\Pdf\Builder\InvoicePdfBuilder;use NextPDF\Symfony\Message\GeneratePdfMessage;
$bus->dispatch(new GeneratePdfMessage( builderClass: InvoicePdfBuilder::class, outputPath: $projectDir . '/var/pdfs/invoice-1234.pdf', builderContext: [ 'title' => 'Invoice 1234', 'html' => '<h1>Invoice 1234</h1>', ],));ภายใน builderContext อย่าใส่ Doctrine entity, สตรีมที่เปิดอยู่, closure, ออบเจกต์คำขอ หรือออบเจกต์เซอร์วิส
จุดขยาย
หัวข้อที่มีชื่อว่า “จุดขยาย”| จุดขยาย | ใช้สำหรับ | ข้อจำกัด |
|---|---|---|
PdfFactory สำหรับการตกแต่ง (decoration) เซอร์วิส | การใช้ค่าเริ่มต้นของแอปพลิเคชันก่อนส่งเอกสารถึง controller | ต้องรักษาความหมายของการคืนเอกสารฉบับใหม่ (fresh-document) ไว้ |
PdfBuilderInterface | การนิยาม builder เอกสารแบบเข้าคิวหรือแบบนำกลับมาใช้ใหม่ได้ | ต้องคืนค่าเป็น Document กลับมา |
OptionalExtensionPass | การเปิดใช้คุณสมบัติ Artisan หรือ Premium ที่เลือกได้ในเวลาคอมไพล์ | ความพร้อมใช้งานเป็นสถานะของการคอมไพล์คอนเทนเนอร์ ไม่ใช่สถานะของคำขอ |
| ผัง config ของ Symfony | ค่าเริ่มต้น, PDF/A, การตั้งค่าตัวเรนเดอร์, ลายเซ็น, time-stamping authority (TSA) และ Messenger | config ที่ไม่ถูกต้องควรล้มเหลวระหว่างการสร้างคอนเทนเนอร์ |
GeneratePdfHandler สำหรับการเชื่อมต่อเซอร์วิส | การจำกัดว่า builder ใดเข้าถึงได้จากข้อความที่เข้าคิว | service locator ควรเปิดเผยเฉพาะเซอร์วิส builder ที่ได้รับอนุมัติเท่านั้น |
ขั้นตอนการพัฒนา
หัวข้อที่มีชื่อว่า “ขั้นตอนการพัฒนา”- เพิ่มเซอร์วิส builder ที่รับข้อมูลนำเข้าแบบกำหนดได้แน่นอน
- ใช้
PdfFactory::create()ใน controller หรือเซอร์วิส - เพิ่มการทดสอบ response สำหรับชื่อไฟล์ ประเภทเนื้อหา และ header
- ลงทะเบียน builder กับ Messenger เมื่อจำเป็นต้องสร้างเอกสารเดียวกันแบบอะซิงโครนัส
- เพิ่มการทดสอบข้อความที่ไม่ถูกต้องสำหรับชื่อคลาส พาธเอาต์พุต และรูปแบบบริบท
- เพิ่มการทดสอบการคอมไพล์คอนเทนเนอร์ด้วยการกำหนดค่าขั้นต่ำและการกำหนดค่าสำหรับใช้งานจริง
- วัดเวลาในการเรนเดอร์และหน่วยความจำภายใต้การตั้งค่า PHP เดียวกับการใช้งานจริง
การจัดการความล้มเหลว
หัวข้อที่มีชื่อว่า “การจัดการความล้มเหลว”| ความล้มเหลว | ตำแหน่งที่ควรจัดการ | การตอบสนองที่แนะนำ |
|---|---|---|
| config ที่ไม่ถูกต้อง | การคอมไพล์คอนเทนเนอร์ | ทำให้การ deploy ล้มเหลวก่อนที่ทราฟฟิกจะถึงแอป |
| เซอร์วิส builder ที่ขาดหายไป | การทดสอบ Messenger handler และแท็กเซอร์วิส | ทำให้ข้อความล้มเหลวและแจ้งเตือนทีมที่รับผิดชอบ |
| พาธเอาต์พุตที่ไม่ปลอดภัย | ตัวสร้าง (constructor) ของข้อความและนโยบายการจัดเก็บ | ปฏิเสธก่อนส่งงาน และคงการตรวจสอบใน handler ไว้เพื่อป้องกันเชิงลึก |
| extension ที่เลือกได้ไม่พร้อมใช้งาน | compiler pass และพฤติกรรมของ factory | ปิดใช้คุณสมบัติที่เลือกได้ หรือทำให้ขั้นตอนการติดตั้งชัดเจน |
| การ resolve เซอร์วิสหรือการเรนเดอร์ล้มเหลว | ขอบเขตของ builder | ล้มเหลวแบบปิด (fail closed) เว้นแต่กรณีการใช้งานจะมีกลไกสำรอง (fallback) ที่จัดทำเป็นเอกสารไว้ |
ค่าเริ่มต้นที่ปลอดภัย
หัวข้อที่มีชื่อว่า “ค่าเริ่มต้นที่ปลอดภัย”| ประเด็น | ค่าเริ่มต้น | เมื่อใดควรแทนที่ |
|---|---|---|
| อายุการใช้งานของ factory | เซอร์วิสในคอนเทนเนอร์ | คงค่านี้ไว้ เพราะ factory ปลอดภัยเนื่องจากสร้างเอกสารฉบับใหม่ทุกครั้ง |
| อายุการใช้งานของเอกสาร | หนึ่งรอบงาน (unit of work) | อย่าใช้ร่วมกันข้ามคำขอหรือข้อความ |
| การตรวจสอบพาธเอาต์พุต | ตัวสร้างของข้อความและ handler | เพิ่มข้อจำกัดของผู้เช่า (tenant) หรือรากที่จัดเก็บในโค้ดของแอปพลิเคชัน |
| ชื่อไฟล์ของ response | document.pdf | แทนที่ด้วยตัวระบุทางธุรกิจที่ผ่านการทำให้ปลอดภัย (sanitized) แล้ว |
| การขนส่ง (transport) ของ Messenger | async | ใช้การขนส่งเฉพาะเมื่องาน PDF มีปริมาณมาก |
รายการตรวจสอบการทดสอบ
หัวข้อที่มีชื่อว่า “รายการตรวจสอบการทดสอบ”- ทดสอบว่าคอนเทนเนอร์คอมไพล์ bundle ได้ด้วยการกำหนดค่าขั้นต่ำและการกำหนดค่าสำหรับใช้งานจริง
- การทดสอบ response ยืนยัน security header และการจัดการชื่อไฟล์
- การทดสอบ Messenger ยืนยันว่าพาธที่ไม่ถูกต้องและชื่อคลาส builder ที่ไม่ถูกต้องจะล้มเหลวก่อนการส่งงาน
- การทดสอบ handler ใช้เซอร์วิส builder จริงและไดเรกทอรีเอาต์พุตชั่วคราว
- การทดสอบ builder เรนเดอร์เอกสารตัวแทน และบันทึกภายใต้สิทธิ์ของระบบไฟล์ที่คล้ายกับการใช้งานจริง
- การทดสอบ extension ที่เลือกได้ครอบคลุมกรณี Artisan ไม่พร้อมใช้งาน, Premium ไม่พร้อมใช้งาน และพฤติกรรมของโปรไฟล์ PDF/A ที่กำหนดค่าไว้