Lewati ke konten

Contracts / Streaming

Domain streaming mencakup dua antarmuka experimental: StreamingWriterInterface untuk menghasilkan PDF secara bertahap dan CursorInterface untuk menyusun konten pada tingkat halaman. Core menyertakan mesin final yang teruji dan mengimplementasikan keduanya. Kelas-kelas mesin bersifat internal, jadi Anda menggunakan kontrak experimental publik alih-alih mengimplementasikan mesin itu sendiri. Karena tingkatannya adalah experimental, kontrak ini dapat berubah pada rilis minor dengan pemberitahuan penghentian sebelumnya. Kunci versinya secara ketat atau bungkus di balik adapter Anda sendiri sebelum mengandalkannya di produksi.

Terminal window
composer require nextpdf/core:^3

Streaming writer melakukan serialisasi setiap halaman saat Anda menyusunnya dan dapat membilasnya ke keluaran sebelum halaman berikutnya dimulai. Gunakan ini ketika sebuah dokumen dapat melampaui anggaran memori yang tersedia. Writer dalam memori menyimpan seluruh dokumen; streaming writer tidak. StreamingWriterInterface mendefinisikan mesin status yang ketat. Instance baru berstatus CLOSED. open() memindahkannya ke OPEN dan menulis header PDF ke stream yang disediakan oleh pemanggil. newPage() memindahkannya ke PAGING dan mengembalikan cursor. close() menulis struktur referensi-silang dan trailer, lalu memindahkannya ke status terminal CLOSED. Stream referensi-silang memetakan setiap nomor objek ke offset byte-nya, sebagaimana tercakup dalam ISO 32000-2 §7. Setiap instance hanya dapat menjalankan satu sesi. Setelah close(), instance tersebut tidak dapat digunakan lagi. Sumber daya stream tetap menjadi milik pemanggil. Writer menulis ke stream tersebut, tetapi tidak pernah menutupnya.

CursorInterface adalah permukaan penulisan pada tingkat halaman. Anda memperoleh cursor dari StreamingWriterInterface::newPage(), dan cursor tetap valid hingga Anda menyelesaikannya, hingga newPage() berikutnya menyelesaikannya secara otomatis, atau hingga close() membuatnya tidak valid. Status tidak valid bersifat permanen. Sebuah cursor tidak dapat diaktifkan kembali. Setiap metode yang dipanggil pada cursor yang sudah tidak valid akan melempar LogicException. Cursor menulis operator content-stream mentah, menetapkan font aktif, dan menulis teks yang diposisikan. Sebuah content stream mengodekan konten halaman sebagai rangkaian operator grafis, sebagaimana tercakup dalam ISO 32000-2 §8. Cursor adalah permukaan tingkat rendah: ia tidak melakukan pembentukan teks, penataan ulang dua arah, pemenggalan baris, atau tata letak apa pun. Hal-hal itu tetap menjadi tanggung jawab pada tingkat Document. Invarian cursor tunggal berlaku sepanjang waktu: paling banyak satu cursor yang valid pada saat tertentu.

Kedua antarmuka tersebut bersifat experimental, dan Core menyertakan mesin yang berfungsi di baliknya: implementasi StreamingWriterInterface yang final, cursor halamannya, dan discard sink yang digunakan untuk benchmarking memori. Kelas-kelas mesin ini bersifat internal dan bukan bagian dari permukaan publik. Untuk menggunakan streaming, bergantunglah pada kontrak experimental dan biarkan Core menyediakan implementasinya. PHPDoc pada setiap tipe mengarah ke ADR streaming-writer untuk mesin status siklus hidup dan dasar pemikiran cakupannya. Karena tingkatannya adalah experimental, tanda tangan kontrak masih dapat berubah pada rilis minor dengan pemberitahuan penghentian sebelumnya. Kunci versinya secara ketat atau bungkus di balik adapter Anda sendiri sebelum mengandalkannya di produksi.

TipeJenisAnggota utamaStabilitasSejak
StreamingWriterInterfaceinterfaceopen(resource, Config), newPage(?PageSize): CursorInterface, close()experimental (mesin yang disertakan)3.1.0
CursorInterfaceinterfacewriteContent(string), setFont(string, string, float), writeText(float, float, string), finalizePage()experimental (mesin yang disertakan)3.1.0

open() melempar InvalidArgumentException untuk stream yang tidak dapat ditulis dan LogicException jika writer sudah terbuka. close() tidak idempoten. Memanggilnya dua kali akan melempar pengecualian.

examples/contracts/streaming-quickstart.php
<?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.
}

Fungsi tersebut menargetkan antarmuka experimental, sehingga tetap terpisah dari kelas mesin. Core menyuntikkan implementasi yang berfungsi di lokasi pemanggilan.

examples/contracts/streaming-production.php
<?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();
}
}
}

Blok finally menjamin writer ditutup dan trailer ditulis, bahkan ketika perulangan halaman melempar pengecualian. Stream tetap menjadi milik pemanggil dan ditutup oleh pemanggil.

  • Bergantunglah pada antarmuka, bukan pada kelas mesin. Mesin yang mengimplementasikan kedua kontrak tersebut bersifat internal dan bukan bagian dari permukaan publik. Jangan meng-new-kannya atau merujuknya berdasarkan nama. Beri petunjuk tipe StreamingWriterInterface dan biarkan Core menyediakan implementasinya.
  • Kontrak ini bersifat experimental. Tanda tangannya dapat berubah pada rilis minor, dengan pemberitahuan penghentian sebelumnya. Kunci versinya secara ketat atau bungkus di balik adapter Anda sendiri sebelum mengandalkannya di produksi.
  • Sebuah cursor menjadi tidak valid saat newPage() berikutnya atau close() dipanggil. Menyimpan cursor yang sudah usang dan memanggil metodenya akan melempar LogicException. Selesaikan secara eksplisit demi kejelasan.
  • close() tidak idempoten. Memanggilnya dua kali adalah bug pemanggil, bukan kondisi yang dapat dipulihkan. Kontrak akan melempar pengecualian.
  • Writer tidak pernah menutup stream. Jika Anda lupa menutup handle milik pemanggil setelah close() kembali, Anda membocorkan file descriptor.
  • Mesin membilas setiap halaman yang telah diselesaikan sehingga memori residen tidak bertambah seiring jumlah halaman. Profil memori pastinya adalah properti tingkat experimental dan dapat bergeser antar-rilis minor. Jangan menjadikan asumsi dari satu pengukuran sebagai ketergantungan permanen.

Desain streaming membatasi memori puncak. Mesin yang disertakan membilas setiap halaman yang telah selesai dan melepaskan buffer-nya, sehingga resident set tidak bertambah seiring jumlah halaman, tidak seperti writer dalam memori. Mesin melimpahkan pembukuan referensi-silang dan page-tree-nya ke stream sementara berbasis disk agar jejak proses tetap mendekati konstan. Angka memori dan waktu nyata yang konkret adalah properti tingkat experimental dan dapat berubah antar-rilis minor, sehingga halaman ini tidak menyatakan angka tetap apa pun. performance_budget sebesar 1500 ms waktu nyata dan puncak 64 MB adalah batas canvas, bukan jaminan kontraktual. Reproduksibilitas bersifat bitwise: konten dan konfigurasi yang sama menghasilkan keluaran yang identik byte demi byte, yang dikunci oleh uji golden-baseline mesin.

Metode writeContent() pada cursor adalah jalan pintas tingkat rendah. Metode ini menambahkan byte yang diberikan ke content stream halaman secara apa adanya dan tidak memvalidasi sintaks atau semantik operator. Input tidak tepercaya yang diteruskan ke writeContent() menghasilkan PDF yang rusak atau berbahaya. Perlakukan metode tersebut sebagai permukaan yang hanya menerima input tepercaya, dan utamakan writeText() untuk teks apa pun yang dipengaruhi pemanggil. Cursor yang disertakan melakukan escaping pada teks yang diteruskan ke writeText() untuk tata bahasa literal-string PDF, tetapi tidak menyanitasi operator mentah. Model stream milik pemanggil juga merupakan properti keamanan. Mesin menulis ke stream tetapi tidak pernah menutup atau membukanya kembali, sehingga tidak dapat mengalihkan keluaran. Permukaan serangan runtime bersifat nyata karena mesin tersebut disertakan. Pemanggil tidak boleh memberikan byte tidak tepercaya ke writeContent(), dan mesin harus mematuhi invarian kontrak.

KlaimStandarKlausulBukti
Sebuah content stream mengodekan konten halaman sebagai rangkaian operator grafis, yang ditambahkan oleh cursor.ISO 32000-2§8
Writer memancarkan struktur referensi-silang yang memetakan setiap nomor objek ke offset byte-nya saat penutupan.ISO 32000-2§7

Kedua klausul tersebut dikunci ke glosarium dan disajikan sebagai parafrasa. NextPDF tidak mereproduksi teks normatif apa pun. ADR streaming-writer yang dirujuk oleh PHPDoc kontrak memuat dasar pemikiran siklus hidup dan cakupannya.

Sebuah mesin streaming yang teruji disertakan dalam Core sumber terbuka di balik kontrak experimental ini. Kelas-kelas mesin bersifat internal, jadi Anda menggunakan streaming melalui kontrak publik alih-alih nama kelas konkret. NextPDF Pro dan NextPDF Enterprise mengikuti kontrak yang sama, sehingga kode yang ditulis terhadap StreamingWriterInterface di Core tetap valid terhadap implementasi Premium dari kontrak yang sama. Peringatannya ada pada tingkatan experimental, bukan pada edisi atau ketersediaan. Tanda tangannya dapat berubah pada rilis minor dengan pemberitahuan penghentian sebelumnya.