Các ràng buộc luồng một lượt cho HTML (ADR-001)
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”NextPDF kết xuất HyperText Markup Language (HTML) trong một lượt duy nhất theo chiều tiến và không giữ cây phần tử nào trong bộ nhớ. ADR-001 ghi lại lựa chọn đó cùng các ràng buộc mà nó đặt ra cho mọi tính năng Cascading Style Sheets (CSS).
Cài đặt
Phần tiêu đề “Cài đặt”composer require nextpdf/core:^3Tổng quan khái niệm
Phần tiêu đề “Tổng quan khái niệm”Hệ thống con HTML kết xuất HTML và CSS thành Portable Document Format (PDF) trong một lượt luồng duy nhất. ADR-001 (“Stream-based Rendering Pipeline Retention”, được chấp thuận ngày 2026-04-06) là quyết định kiến trúc định nghĩa mô hình này. Trang này giải thích mô hình, các ranh giới của mô hình và những ràng buộc mà người đóng góp phải tuân thủ.
Trong mô hình này, bộ token hóa (HtmlTokenizer) đọc đầu vào một lần và tạo ra một danh sách token phẳng. HtmlParser::processTokens() duyệt danh sách đó từ trái sang phải và ghi các toán tử luồng nội dung PDF vào một bộ đệm chuỗi khi đi đến từng phần tử. Engine không xây dựng đồ thị phần tử bền vững nào giữa các lần gọi. Mọi trạng thái cần tồn tại qua một lần gọi trình xử lý đều được chuyển qua một value object dạng ảnh chụp (HtmlBlockCursor), chứ không qua các nút dùng chung. Việc kế thừa kiểu sử dụng một ngăn xếp push-and-pop gồm các thực thể HtmlStyleState phẳng, không phải một cây con trỏ cha.
Đây không phải là mô hình giữ tài liệu. Engine không giữ cây tài liệu, không bố cục lại nội dung đã ghi và không cho phép đầu vào thay đổi sau khi quá trình phân tích bắt đầu. Ranh giới rất rõ ràng: NextPDF truyền luồng từ đầu đến cuối. Một bộ kết xuất kiểu giữ tài liệu sẽ dựng toàn bộ tài liệu trong bộ nhớ trước; NextPDF thì không.
Hai thao tác cần một mức nhìn trước có giới hạn. Cả hai đều là ngoại lệ tường minh và bị giới hạn. Việc định kích thước cột của bảng quét qua mọi hàng trước khi đặt một ô. Nó đệm các hàng đó trong một bộ đệm bảng tạm thời bên trong TableParser, một ngoại lệ được ADR-001 nêu đích danh. Bộ chọn quan hệ :has() cùng các bộ chọn :last-child và :last-of-type dùng một lượt quét trước có giới hạn trên danh sách token phẳng, chứ không duyệt cây. ADR-001 ghi lại cả hai ngoại lệ và các giới hạn của chúng.
Mô hình này an toàn với worker. HtmlParser được khởi tạo một lần cho mỗi yêu cầu, không bao giờ ở dạng singleton. HtmlParser::parse() đặt lại mọi trường ở đầu mỗi lần gọi. Không có trạng thái tĩnh có thể thay đổi nào trong đường kết xuất, vì vậy RoadRunner, Swoole và Laravel Octane có thể tái dùng tiến trình mà không làm rò rỉ trạng thái giữa các tài liệu.
Bề mặt API
Phần tiêu đề “Bề mặt API”Các ký hiệu dưới đây thực thi những ràng buộc này. Hãy kiểm chứng từng mục với src/Html/.
| Ký hiệu | Vị trí | Vai trò |
|---|---|---|
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | Điểm vào. Đặt lại toàn bộ trạng thái, rồi chạy lượt duy nhất. |
HtmlParser::MAX_ELEMENT_COUNT (50_000) | src/Html/HtmlParser.php | Giới hạn cứng về số phần tử được xử lý. |
HtmlParser::MAX_NESTING_DEPTH (100) | src/Html/HtmlParser.php | Giới hạn cứng về độ lồng nhau. |
HtmlBlockCursor | src/Html/HtmlBlockCursor.php | Ảnh chụp con trỏ. Cơ chế trạng thái dùng chung duy nhất. |
HtmlStyleState | src/Html/HtmlStyleState.php | Khung kiểu được đẩy vào ngăn xếp. Không có con trỏ cha. |
TableParser::reset() | src/Html/TableParser.php | Bắt buộc đặt lại bộ đệm bảng tạm thời giữa các bảng. |
Mẫu mã — bắt đầu nhanh
Phần tiêu đề “Mẫu mã — bắt đầu nhanh”Bạn không quản lý trực tiếp mô hình luồng. Một lần gọi sẽ kết xuất bất kỳ tài liệu nào được hỗ trợ.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Streaming render');$doc->addPage();$doc->writeHtml('<h1>One forward pass</h1><p>No retained tree.</p>');$doc->save(__DIR__ . '/output/streaming.pdf');Mẫu mã — môi trường sản xuất
Phần tiêu đề “Mẫu mã — môi trường sản xuất”Kết xuất một tài liệu lớn trong giới hạn bộ nhớ cố định. Giới hạn về số phần tử là ranh giới an toàn, vì vậy hãy ước lượng kích thước đầu vào trước khi gọi.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\HtmlParsingException;
/** * Render trusted HTML, surfacing the streaming-model limits as typed errors. * * @param non-empty-string $html */function renderReport(string $html, string $out): void{ $doc = Document::createStandalone(); $doc->addPage();
try { $doc->writeHtml($html); } catch (HtmlParsingException $e) { // Thrown on the 10 MB input cap, the 50,000-element cap, // or the 100-level nesting cap. These are model boundaries, // not transient faults — do not retry. throw $e; }
$doc->save($out);}Trường hợp biên và điểm cần lưu ý
Phần tiêu đề “Trường hợp biên và điểm cần lưu ý”- Giới hạn số phần tử là điểm dừng cứng. Engine ném ra
HtmlParsingExceptiontạiMAX_ELEMENT_COUNT = 50_000. Hãy chia các báo cáo rất lớn thành nhiều lần gọiwriteHtml()hoặc nhiều tài liệu. - Giới hạn độ lồng nhau là điểm dừng cứng. Độ sâu vượt quá
MAX_NESTING_DEPTH = 100sẽ ném ngoại lệ. Các lớp bao bọc lồng quá sâu thường là nguyên nhân gây ra điều này. - Giới hạn kích thước đầu vào.
HtmlParser::parse()từ chối đầu vào lớn hơn 10 MB trước khi token hóa. :has()bị kiểm soát. Lượt quét trước của:has()chỉ chạy khi tính năng thử nghiệmcss.hasđang được bật. Nếu không có nó, các bộ chọn:has()sẽ không khớp.- Việc đệm bảng là cây tạm thời duy nhất. Một bảng đơn lẻ rất rộng hoặc rất cao sẽ giữ các hàng của nó trong bộ nhớ cho đến khi
render().TableParsergiới hạn bộ đệm này theo từng bảng và đặt lại giữa các bảng; đây không phải là một cây trên toàn tài liệu. - Không bố cục lại. Nội dung đã được ghi sẽ không bao giờ bị di chuyển. Một kiểu xuất hiện muộn không thể thay đổi ngược phần đầu ra trước đó.
Hiệu năng
Phần tiêu đề “Hiệu năng”Mô hình luồng giữ tối đa một HtmlStyleState cho mỗi mức lồng nhau, bị giới hạn bởi MAX_NESTING_DEPTH = 100, cộng với các trường con trỏ đang hoạt động. Bộ nhớ dành cho trạng thái kiểu và con trỏ là O(depth), không phải O(element count). ADR-001 ghi lại ý đồ thiết kế rằng mức này luôn thấp hơn hẳn so với một đồ thị đối tượng được giữ lại cho cùng một đầu vào. Benchmark đỉnh resident set size (RSS) được kiểm soát với 50,000 phần tử là mục tiêu kiểm định thực nghiệm được nêu trong ADR-001. Benchmark hiệu năng của đường kết xuất HTML theo dõi chỉ số này bằng một cổng kiểm soát hồi quy 5% (công việc đã hợp nhất, PR #564). Hãy xem performance_budget theo từng trang (wall_ms: 1500, peak_mb: 64) là trần vận hành.
Ghi chú bảo mật
Phần tiêu đề “Ghi chú bảo mật”Các giới hạn trên trang này cũng đóng vai trò là biện pháp kiểm soát từ chối dịch vụ. DefaultHtmlSecurityPolicy thực thi trần đầu vào 10 MB và trần lồng nhau 100 mức độc lập với bộ phân tích, nên một tài liệu độc hại không thể làm cạn bộ nhớ thông qua độ sâu hoặc kích thước. Bản thân mô hình luồng giới hạn bộ nhớ ngay từ thiết kế: không có đồ thị phần tử nào để kẻ tấn công làm phình to. Hãy xem mô hình bảo mật của mô-đun HTML và các hợp đồng lớp để biết toàn bộ bề mặt chính sách.
Tuân thủ
Phần tiêu đề “Tuân thủ”Trang này không trích dẫn tiêu chuẩn bên ngoài nào. Các ràng buộc này đến từ ADR-001 và từ các ký hiệu mã nguồn thực thi được liệt kê trong mục Bề mặt API. Các ánh xạ hành vi theo đặc tả CSS được ghi trong tài liệu tại css-resolver, không ghi ở đây.
Bối cảnh thương mại
Phần tiêu đề “Bối cảnh thương mại”Khả năng cho doanh nghiệp. Kiến trúc luồng giống hệt nhau giữa Core và Premium. Premium mở rộng phạm vi hỗ trợ CSS; nó không thay đổi mô hình một lượt và cũng không nới lỏng các giới hạn này. Hãy xem ma trận hỗ trợ CSS.