Contracts / Streaming
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”โดเมน streaming ประกอบด้วยอินเทอร์เฟซ experimental สองรายการ ได้แก่ StreamingWriterInterface สำหรับสร้างเอาต์พุต PDF แบบเพิ่มทีละส่วน และ CursorInterface สำหรับประกอบเนื้อหาในระดับหน้า Core มาพร้อมเอนจิน final ที่ทดสอบแล้วและทำงานตามอินเทอร์เฟซทั้งสองรายการ คลาสของเอนจินเป็น internal ดังนั้นให้ใช้สัญญาสาธารณะ experimental แทนการสร้างเอนจินเอง เนื่องจากมีระดับเป็น experimental สัญญาอาจเปลี่ยนแปลงได้ในรุ่นย่อยพร้อมประกาศการเลิกใช้ล่วงหน้า ให้ตรึงเวอร์ชันอย่างเข้มงวดหรือห่อหุ้มไว้หลังอะแดปเตอร์ของคุณเองก่อนนำไปพึ่งพาในการใช้งานจริง
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/core:^3ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”streaming writer จะซีเรียลไลซ์แต่ละหน้าระหว่างที่คุณประกอบหน้านั้น และอาจฟลัชหน้าไปยังเอาต์พุตก่อนเริ่มหน้าถัดไป ใช้เมื่อเอกสารอาจมีขนาดเกินงบประมาณหน่วยความจำที่มีอยู่ in-memory writer จะเก็บเอกสารทั้งฉบับไว้ แต่ streaming writer ไม่ทำเช่นนั้น StreamingWriterInterface กำหนด state machine ที่เข้มงวด อินสแตนซ์ที่สร้างใหม่จะอยู่ในสถานะ CLOSED open() เปลี่ยนสถานะเป็น OPEN และเขียนส่วนหัว PDF ลงในสตรีมที่ผู้เรียกจัดเตรียมให้ newPage() เปลี่ยนสถานะเป็น PAGING และคืนค่า cursor close() เขียนโครงสร้าง cross-reference และ trailer จากนั้นเปลี่ยนสถานะไปยังสถานะปลายทาง CLOSED cross-reference stream จับคู่หมายเลขออบเจกต์แต่ละรายการกับออฟเซตไบต์ของออบเจกต์นั้น ตามที่ครอบคลุมใน ISO 32000-2 §7 แต่ละอินสแตนซ์รันได้เพียงหนึ่งเซสชัน หลังจาก close() แล้วอินสแตนซ์จะใช้งานต่อไม่ได้ ผู้เรียกเป็นเจ้าของทรัพยากรสตรีม writer เขียนลงในสตรีมแต่ไม่ปิดสตรีมนั้น
CursorInterface คือพื้นผิวการเขียนระดับหน้า คุณได้ cursor จาก StreamingWriterInterface::newPage() และ cursor จะยังใช้งานได้จนกว่าคุณจะ finalize มัน จนกว่า newPage() ครั้งถัดไปจะ finalize ให้โดยอัตโนมัติ หรือจนกว่า close() จะทำให้ใช้งานไม่ได้ การทำให้ใช้งานไม่ได้เป็นแบบถาวร cursor ไม่สามารถเปิดใช้งานใหม่ได้ ทุกเมธอดบน cursor ที่ใช้งานไม่ได้แล้วจะโยน LogicException cursor เขียนตัวดำเนินการ content-stream แบบดิบ ตั้งค่าฟอนต์ที่ใช้งานอยู่ และเขียนข้อความตามตำแหน่งที่กำหนด content stream เข้ารหัสเนื้อหาหน้าเป็นลำดับของตัวดำเนินการกราฟิก ตามที่ครอบคลุมใน ISO 32000-2 §8 cursor เป็นพื้นผิวระดับล่าง และไม่ทำ text shaping การเรียงลำดับแบบสองทิศทาง การตัดบรรทัด หรือเค้าโครงใดๆ สิ่งเหล่านั้นยังคงเป็นหน้าที่ในระดับ Document ค่าคงที่ single-cursor มีผลตลอด กล่าวคือมี cursor ที่ใช้งานได้อย่างมากที่สุดเพียงหนึ่งตัวในเวลาใดเวลาหนึ่ง
อินเทอร์เฟซทั้งสองรายการเป็น experimental และ Core มาพร้อมเอนจินที่ใช้งานได้อยู่เบื้องหลัง ได้แก่ implementation final ของ StreamingWriterInterface, page cursor ของเอนจินนั้น และ discard sink ที่ใช้สำหรับ benchmark หน่วยความจำ คลาสเอนจินเหล่านี้เป็น internal และไม่ได้เป็นส่วนหนึ่งของพื้นผิวสาธารณะ หากต้องการใช้ streaming ให้พึ่งพาสัญญา experimental และปล่อยให้ Core จัดหา implementation นั้นให้ PHPDoc บนแต่ละชนิดชี้ไปยัง streaming-writer ADR สำหรับ state machine ของวงจรชีวิตและเหตุผลด้านขอบเขต เนื่องจากมีระดับเป็น experimental ลายเซ็นของสัญญายังอาจเปลี่ยนแปลงได้ในรุ่นย่อยพร้อมประกาศการเลิกใช้ล่วงหน้า ให้ตรึงเวอร์ชันอย่างเข้มงวดหรือห่อหุ้มไว้หลังอะแดปเตอร์ของคุณเองก่อนนำไปพึ่งพาในการใช้งานจริง
พื้นผิว API
หัวข้อที่มีชื่อว่า “พื้นผิว API”| ชนิด | ประเภท | สมาชิกหลัก | เสถียรภาพ | ตั้งแต่ |
|---|---|---|---|---|
StreamingWriterInterface | interface | open(resource, Config), newPage(?PageSize): CursorInterface, close() | experimental (เอนจินที่จัดส่งแล้ว) | 3.1.0 |
CursorInterface | interface | writeContent(string), setFont(string, string, float), writeText(float, float, string), finalizePage() | experimental (เอนจินที่จัดส่งแล้ว) | 3.1.0 |
open() โยน InvalidArgumentException สำหรับสตรีมที่เขียนไม่ได้ และโยน LogicException หาก writer เปิดอยู่แล้ว close() ไม่เป็น idempotent การเรียกซ้ำสองครั้งจะโยนข้อยกเว้น
ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\StreamingWriterInterface;use NextPDF\Core\Config;
/** * Drive a streaming writer through one page. * * The parameter is the experimental contract; Core supplies the * implementation. Type-hint the interface and let the engine satisfy it. * * @param StreamingWriterInterface $writer A Core-supplied streaming writer. * @param resource $stream A writable, caller-owned stream. */function writeOnePage(StreamingWriterInterface $writer, $stream): void{ $writer->open($stream, new Config()); $cursor = $writer->newPage(); $cursor->setFont('helvetica', '', 12.0); $cursor->writeText(72.0, 720.0, 'Streamed page.'); $cursor->finalizePage(); $writer->close(); // The caller closes $stream after close() returns.}ฟังก์ชันนี้มุ่งเป้าไปที่อินเทอร์เฟซ experimental จึงไม่ผูกติดกับคลาสของเอนจิน Core ฉีด implementation ที่ใช้งานได้เข้ามา ณ จุดเรียกใช้
ตัวอย่างโค้ด — การใช้งานจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — การใช้งานจริง”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\StreamingWriterInterface;use NextPDF\Core\Config;use NextPDF\ValueObjects\PageSize;use Psr\Log\LoggerInterface;
final readonly class LargeReportStreamer{ public function __construct( private StreamingWriterInterface $writer, private LoggerInterface $logger, ) {}
/** * Stream a multi-page report to a caller-owned file handle. * * @param resource $stream Writable file handle owned by the caller. * @param list<list<string>> $pages One list of text lines per page. */ public function stream($stream, array $pages): void { $this->writer->open($stream, new Config());
try { foreach ($pages as $lines) { $cursor = $this->writer->newPage(PageSize::A4()); $cursor->setFont('helvetica', '', 11.0);
$y = 760.0; foreach ($lines as $line) { $cursor->writeText(72.0, $y, $line); $y -= 14.0; }
$cursor->finalizePage(); } } finally { $this->writer->close(); } }}บล็อก finally รับประกันว่า writer จะปิดและมีการเขียน trailer แม้ว่าลูปของหน้าจะโยนข้อยกเว้นก็ตาม ผู้เรียกยังคงเป็นเจ้าของสตรีมและเป็นผู้ปิดสตรีมนั้น
กรณีขอบเขตและข้อควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบเขตและข้อควรระวัง”- ให้พึ่งพาอินเทอร์เฟซ ไม่ใช่คลาสของเอนจิน เอนจินที่ implement สัญญาทั้งสองรายการเป็น internal และไม่ได้เป็นส่วนหนึ่งของพื้นผิวสาธารณะ อย่า
newมันหรืออ้างอิงถึงมันด้วยชื่อ ให้ type-hint เป็นStreamingWriterInterfaceและปล่อยให้ Core จัดหา implementation ให้ - สัญญานี้เป็น
experimentalลายเซ็นของสัญญาอาจเปลี่ยนแปลงได้ในรุ่นย่อย พร้อมประกาศการเลิกใช้ล่วงหน้า ให้ตรึงเวอร์ชันอย่างเข้มงวดหรือห่อหุ้มไว้หลังอะแดปเตอร์ของคุณเองก่อนนำไปพึ่งพาในการใช้งานจริง - cursor จะใช้งานไม่ได้ทันทีที่มีการเรียก
newPage()หรือclose()ครั้งถัดไป การถือ cursor ที่ค้างอยู่แล้วเรียกเมธอดบนมันจะโยนLogicExceptionให้ finalize อย่างชัดเจนเพื่อความชัดเจน close()ไม่เป็น idempotent การเรียกซ้ำสองครั้งเป็นข้อผิดพลาดของผู้เรียก ไม่ใช่ภาวะที่กู้คืนได้ สัญญาจะโยนข้อยกเว้น- writer ไม่ปิดสตรีม หากคุณลืมปิดแฮนเดิลที่ผู้เรียกเป็นเจ้าของหลังจาก
close()คืนค่า คุณจะทำให้ file descriptor รั่ว - เอนจินจะฟลัชแต่ละหน้าที่ finalize แล้ว เพื่อให้หน่วยความจำหลักไม่เพิ่มขึ้นตามจำนวนหน้า โปรไฟล์หน่วยความจำที่แน่นอนเป็นคุณสมบัติระดับ
experimentalและอาจเปลี่ยนแปลงได้ระหว่างรุ่นย่อย อย่าฮาร์ดโค้ดสมมติฐานจากการวัดเพียงครั้งเดียว
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”การออกแบบ streaming จำกัดเพดานหน่วยความจำ เอนจินที่จัดส่งจะฟลัชแต่ละหน้าที่เสร็จสมบูรณ์และปล่อยบัฟเฟอร์ของหน้านั้น ดังนั้น resident set จึงไม่เพิ่มขึ้นตามจำนวนหน้า ซึ่งต่างจาก in-memory writer เอนจินจะเทข้อมูลบัญชี cross-reference และ page-tree ลงในสตรีมชั่วคราวที่สำรองด้วยดิสก์ เพื่อให้ memory footprint ของกระบวนการเกือบคงที่ ตัวเลขหน่วยความจำและเวลาจริงเป็นคุณสมบัติระดับ experimental และอาจเปลี่ยนแปลงได้ระหว่างรุ่นย่อย ดังนั้นหน้านี้จึงไม่ยืนยันตัวเลขตายตัวใดๆ performance_budget ที่ 1500 ms wall และ 64 MB peak คือกรอบของแคนวาส ไม่ใช่การรับประกันตามสัญญา ความสามารถในการทำซ้ำเป็นแบบ bitwise คือเนื้อหาและการกำหนดค่าเดียวกันให้เอาต์พุตที่เหมือนกันทุกไบต์ ซึ่งตรึงไว้ด้วยการทดสอบ golden-baseline ของเอนจิน
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”เมธอด writeContent() ของ cursor เป็นทางออกระดับล่าง เมธอดนี้ผนวกไบต์ที่ให้มาเข้ากับ content stream ของหน้าแบบตรงตัว และไม่ตรวจสอบไวยากรณ์หรือความหมายของตัวดำเนินการ อินพุตที่ไม่น่าเชื่อถือซึ่งส่งไปยัง writeContent() จะสร้าง PDF ที่เสียหายหรือเป็นอันตราย ให้ถือว่าเมธอดดังกล่าวเป็นพื้นผิวสำหรับอินพุตที่เชื่อถือได้เท่านั้น และเลือกใช้ writeText() สำหรับข้อความใดๆ ที่ได้รับอิทธิพลจากผู้เรียก cursor ที่จัดส่งจะ escape ข้อความที่ส่งไปยัง writeText() ตามไวยากรณ์ literal-string ของ PDF แต่ไม่ทำความสะอาดตัวดำเนินการแบบดิบ แบบจำลองสตรีมที่ผู้เรียกเป็นเจ้าของก็เป็นคุณสมบัติด้านความปลอดภัยเช่นกัน เอนจินเขียนลงในสตรีมแต่ไม่เคยปิดหรือเปิดสตรีมใหม่ จึงไม่สามารถเปลี่ยนเส้นทางเอาต์พุตได้ พื้นผิวการโจมตีขณะรันไทม์มีอยู่จริงเพราะเอนจินถูกจัดส่งมาด้วย ผู้เรียกต้องไม่ป้อนไบต์ที่ไม่น่าเชื่อถือให้กับ writeContent() โดยเด็ดขาด และเอนจินต้องปฏิบัติตามค่าคงที่ของสัญญา
ความสอดคล้อง
หัวข้อที่มีชื่อว่า “ความสอดคล้อง”| ข้ออ้าง | มาตรฐาน | ข้อกำหนด | หลักฐาน |
|---|---|---|---|
| content stream เข้ารหัสเนื้อหาหน้าเป็นลำดับของตัวดำเนินการกราฟิกที่ cursor ผนวกเข้าไป | ISO 32000-2 | §8 | |
| writer ปล่อยโครงสร้าง cross-reference ที่จับคู่หมายเลขออบเจกต์แต่ละรายการกับออฟเซตไบต์ของออบเจกต์นั้นเมื่อปิด | ISO 32000-2 | §7 |
ข้อกำหนดทั้งสองข้อถูกตรึงด้วยอภิธานศัพท์และถอดความใหม่ NextPDF ไม่ทำซ้ำข้อความเชิงบรรทัดฐานใดๆ streaming-writer ADR ที่อ้างอิงโดย PHPDoc ของสัญญามีเหตุผลด้านวงจรชีวิตและขอบเขต
บริบทเชิงพาณิชย์
หัวข้อที่มีชื่อว่า “บริบทเชิงพาณิชย์”เอนจิน streaming ที่ทดสอบแล้วจัดส่งมาใน Core แบบโอเพนซอร์สเบื้องหลังสัญญา experimental เหล่านี้ คลาสของเอนจินเป็น internal ดังนั้นให้ใช้ streaming ผ่านสัญญาสาธารณะแทนชื่อคลาสที่เป็นรูปธรรม NextPDF Pro และ NextPDF Enterprise ใช้สัญญาเดียวกัน ดังนั้นโค้ดที่เขียนตาม StreamingWriterInterface ใน Core ยังคงใช้ได้กับ implementation แบบ Premium ของสัญญาเดียวกัน ระดับ experimental คือข้อควรระวัง ไม่ใช่รุ่นหรือความพร้อมใช้งาน ลายเซ็นอาจเปลี่ยนแปลงได้ในรุ่นย่อยพร้อมประกาศการเลิกใช้ล่วงหน้า
ดูเพิ่มเติม
หัวข้อที่มีชื่อว่า “ดูเพิ่มเติม”- Contracts: 41 public interfaces (SPI) ครอบคลุมภาพรวม SPI และระดับเสถียรภาพ
- Contracts / Document ครอบคลุม in-memory writer ที่สัญญาเหล่านี้เสริมการทำงานให้
- Writer ครอบคลุมตัวปล่อยออบเจกต์ PDF และ cross-reference
- HTML / Streaming constraints (ADR-001) ครอบคลุมเหตุผลด้านขอบเขตของ streaming
- Performance ครอบคลุมเหตุผลที่จำกัดด้วยหน่วยความจำสำหรับเอาต์พุตแบบ streaming