Batasan streaming sekali jalan HTML (ADR-001)
Sekilas pandang
Bagian berjudul “Sekilas pandang”NextPDF merender HyperText Markup Language (HTML) dalam satu lintasan maju dan tidak menyimpan pohon elemen apa pun di memori. ADR-001 mencatat pilihan ini beserta batasan yang diberlakukannya pada setiap fitur Cascading Style Sheets (CSS).
Pemasangan
Bagian berjudul “Pemasangan”composer require nextpdf/core:^3Tinjauan konseptual
Bagian berjudul “Tinjauan konseptual”Subsistem HTML merender HTML dan CSS menjadi Portable Document Format (PDF) melalui streaming satu lintasan. ADR-001 (“Stream-based Rendering Pipeline Retention”, diterima 2026-04-06) adalah keputusan arsitektur yang mendefinisikan model ini. Halaman ini menjelaskan model tersebut, batasannya, dan ketentuan yang harus dipatuhi para kontributor.
Dalam model ini, tokenizer (HtmlTokenizer) membaca masukan satu kali saja dan menghasilkan daftar token datar. HtmlParser::processTokens() menelusuri daftar tersebut dari kiri ke kanan dan menuliskan operator content-stream PDF ke buffer string saat mencapai setiap elemen. Mesin tidak membangun grafik elemen permanen di antara pemanggilan. Setiap state yang harus bertahan melewati pemanggilan handler dialirkan melalui objek nilai snapshot (HtmlBlockCursor), bukan melalui simpul bersama. Pewarisan gaya menggunakan tumpukan push-and-pop berisi instansi HtmlStyleState datar, bukan pohon penunjuk induk.
Ini bukan model dokumen tertahan (retained-document). Mesin tidak mempertahankan pohon dokumen, tidak menata letak ulang konten yang sudah dituliskannya, dan tidak mengizinkan masukan berubah setelah parse dimulai. Batasannya tegas: NextPDF melakukan streaming dari ujung ke ujung. Perender tertahan membangun seluruh dokumen di memori terlebih dahulu; NextPDF tidak.
Dua operasi memerlukan lookahead terbatas. Keduanya merupakan pengecualian yang eksplisit dan terbatas. Penentuan ukuran kolom tabel memindai setiap baris sebelum menempatkan sel. Operasi ini menyangga baris-baris tersebut dalam buffer tabel sementara di dalam TableParser, sebuah pengecualian yang diakui secara eksplisit oleh ADR-001. Selektor relasional :has() serta selektor :last-child dan :last-of-type menggunakan pra-pindai terbatas atas daftar token datar, bukan penelusuran pohon. ADR-001 mencatat kedua pengecualian tersebut beserta batasannya.
Model ini aman untuk worker. HtmlParser dibangun sekali per permintaan, tidak pernah sebagai singleton. HtmlParser::parse() mengatur ulang setiap field pada awal setiap pemanggilan. Tidak ada state mutabel statis di jalur render, sehingga RoadRunner, Swoole, dan Laravel Octane dapat menggunakan ulang proses tanpa kebocoran state antardokumen.
Permukaan API
Bagian berjudul “Permukaan API”Simbol-simbol berikut menegakkan batasan tersebut. Verifikasi masing-masing terhadap src/Html/.
| Simbol | Lokasi | Peran |
|---|---|---|
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | Titik masuk. Mengatur ulang semua state, lalu menjalankan satu lintasan. |
HtmlParser::MAX_ELEMENT_COUNT (50_000) | src/Html/HtmlParser.php | Batas keras (hard cap) pada elemen yang diproses. |
HtmlParser::MAX_NESTING_DEPTH (100) | src/Html/HtmlParser.php | Batas keras (hard cap) pada kedalaman penyarangan. |
HtmlBlockCursor | src/Html/HtmlBlockCursor.php | Snapshot cursor. Satu-satunya mekanisme state bersama. |
HtmlStyleState | src/Html/HtmlStyleState.php | Bingkai gaya yang didorong ke tumpukan. Tanpa penunjuk induk. |
TableParser::reset() | src/Html/TableParser.php | Pengaturan ulang wajib untuk buffer tabel sementara di antara tabel. |
Contoh kode — Mulai cepat
Bagian berjudul “Contoh kode — Mulai cepat”Anda tidak perlu mengelola model streaming secara langsung. Satu pemanggilan merender dokumen apa pun yang didukung.
<?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');Contoh kode — Produksi
Bagian berjudul “Contoh kode — Produksi”Render dokumen besar dengan anggaran memori tetap. Batas elemen adalah batas keamanan, jadi tentukan ukuran masukan sebelum pemanggilan.
<?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);}Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”- Batas elemen adalah penghentian keras. Mesin melempar
HtmlParsingExceptionpadaMAX_ELEMENT_COUNT = 50_000. Bagi laporan yang sangat besar ke beberapa pemanggilanwriteHtml()atau beberapa dokumen. - Batas penyarangan adalah penghentian keras. Kedalaman di atas
MAX_NESTING_DEPTH = 100akan melempar. Pembungkus yang tersarang terlalu dalam biasanya menjadi penyebabnya. - Batas ukuran masukan.
HtmlParser::parse()menolak masukan yang lebih besar dari 10 MB sebelum tokenisasi. :has()berada di balik feature gate. Pra-pindai:has()hanya berjalan ketika fitur eksperimentalcss.hasaktif. Tanpa itu, selektor:has()tidak akan cocok.- Penyanggaan tabel adalah satu-satunya pohon sementara. Satu tabel yang sangat lebar atau sangat tinggi menahan baris-barisnya di memori sampai
render().TableParsermembatasi buffer ini per tabel dan mengatur ulangnya di antara tabel; ini bukan pohon yang mencakup seluruh dokumen. - Tanpa penataan letak ulang. Konten yang sudah dituliskan tidak pernah dipindahkan. Gaya yang datang belakangan tidak dapat mengubah keluaran sebelumnya secara surut.
Kinerja
Bagian berjudul “Kinerja”Model streaming menahan maksimal satu HtmlStyleState per tingkat penyarangan, dibatasi oleh MAX_NESTING_DEPTH = 100, ditambah field cursor aktif. Memori style-state dan cursor bersifat O(depth), bukan O(jumlah elemen). ADR-001 mencatat tujuan desain agar nilai ini tetap jauh di bawah grafik objek tertahan untuk masukan yang sama. Tolok ukur peak resident set size (RSS) untuk 50,000 elemen terkendali adalah target validasi empiris yang disebutkan dalam ADR-001. Tolok ukur kinerja render-pipeline HTML melacaknya dengan gerbang regresi 5% (pekerjaan yang sudah digabungkan, PR #564). Perlakukan performance_budget per halaman (wall_ms: 1500, peak_mb: 64) sebagai langit-langit operasional.
Catatan keamanan
Bagian berjudul “Catatan keamanan”Batasan pada halaman ini juga berperan sebagai kontrol denial-of-service. DefaultHtmlSecurityPolicy menegakkan batas atas masukan 10 MB dan batas atas penyarangan 100 tingkat secara independen dari parser, sehingga dokumen berbahaya tidak dapat menghabiskan memori melalui kedalaman atau ukuran. Model streaming itu sendiri membatasi memori secara konstruktif: tidak ada grafik elemen yang dapat diperbesar oleh penyerang. Lihat model keamanan modul HTML dan kontrak lapisan untuk permukaan kebijakan selengkapnya.
Kesesuaian
Bagian berjudul “Kesesuaian”Halaman ini tidak mengutip standar eksternal apa pun. Batasan ini berasal dari ADR-001 dan dari simbol sumber yang menegakkannya, yang terdaftar pada Permukaan API. Pemetaan perilaku spesifikasi CSS didokumentasikan pada css-resolver, bukan di sini.
Konteks komersial
Bagian berjudul “Konteks komersial”Kapabilitas Enterprise. Arsitektur streaming identik di Core dan Premium. Premium memperluas cakupan CSS; ini tidak mengubah model sekali jalan maupun melonggarkan batasan tersebut. Lihat matriks dukungan CSS.