Kontrakty / strumieniowanie
W skrócie
Dział zatytułowany „W skrócie”Domena strumieniowania obejmuje dwa interfejsy experimental: StreamingWriterInterface do przyrostowego wyjścia PDF oraz CursorInterface do komponowania treści na poziomie strony. Core dostarcza finalny, przetestowany silnik, który implementuje oba. Klasy silnika są wewnętrzne, więc zamiast implementować silnik samodzielnie, korzystasz z publicznego kontraktu experimental. Ponieważ ten poziom jest experimental, kontrakt może się zmienić w wydaniu pomocniczym po uprzednim powiadomieniu o wycofaniu. Ustaw ścisłe ograniczenie wersji albo opakuj go we własny adapter, zanim uzależnisz od niego środowisko produkcyjne.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Zapis strumieniowy serializuje każdą stronę w trakcie jej komponowania i może wypchnąć ją na wyjście, zanim zacznie się kolejna strona. Używaj go, gdy dokument może przekroczyć dostępny budżet pamięci. Zapis w pamięci przechowuje cały dokument. Zapis strumieniowy tego nie robi. StreamingWriterInterface definiuje ścisły automat stanów. Nowa instancja jest w stanie CLOSED. open() przenosi ją do stanu OPEN i zapisuje nagłówek PDF do strumienia dostarczonego przez wywołującego. newPage() przenosi ją do stanu PAGING i zwraca kursor. close() zapisuje strukturę odsyłaczy oraz zwiastun, a następnie przenosi ją do terminalnego stanu CLOSED. Strumień odsyłaczy odwzorowuje każdy numer obiektu na jego przesunięcie bajtowe, co opisuje ISO 32000-2 §7. Jedna instancja obsługuje tylko jedną sesję. Po wywołaniu close() instancja jest zużyta. Zasobem strumienia zarządza wywołujący. Zapis zapisuje do niego dane, ale nigdy go nie zamyka.
CursorInterface to powierzchnia zapisu na poziomie strony. Kursor uzyskujesz z StreamingWriterInterface::newPage(); pozostaje ważny do czasu finalizacji, do chwili, gdy kolejne newPage() automatycznie go sfinalizuje, albo do chwili, gdy close() go unieważni. Unieważnienie jest trwałe. Kursora nie można ponownie aktywować. Każda metoda unieważnionego kursora zgłasza LogicException. Kursor zapisuje surowe operatory strumienia treści, ustawia aktywną czcionkę i zapisuje tekst w określonej pozycji. Strumień treści koduje zawartość strony jako sekwencję operatorów graficznych, co opisuje ISO 32000-2 §8. Kursor jest powierzchnią niskiego poziomu: nie wykonuje kształtowania tekstu, dwukierunkowego porządkowania, łamania wierszy ani żadnego układu. To pozostaje zadaniem poziomu Document. Niezmiennik pojedynczego kursora obowiązuje przez cały czas: w dowolnym momencie ważny jest co najwyżej jeden kursor.
Oba interfejsy są experimental, a Core dostarcza pod nimi działający silnik: finalną implementację StreamingWriterInterface, jej kursor strony oraz ujście odrzucające używane w testach porównawczych pamięci. Te klasy silnika są wewnętrzne i nie stanowią części powierzchni publicznej. Aby korzystać ze strumieniowania, zależ od kontraktu experimental i pozwól, by Core dostarczył implementację. PHPDoc każdego typu wskazuje ADR zapisu strumieniowego opisujący automat stanów cyklu życia oraz uzasadnienie zakresu. Ponieważ ten poziom jest experimental, sygnatura kontraktu nadal może się zmienić w wydaniu pomocniczym po uprzednim powiadomieniu o wycofaniu. Ustaw ścisłe ograniczenie wersji albo opakuj go we własny adapter, zanim uzależnisz od niego środowisko produkcyjne.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”| Typ | Rodzaj | Kluczowe składowe | Stabilność | Od wersji |
|---|---|---|---|---|
StreamingWriterInterface | interface | open(resource, Config), newPage(?PageSize): CursorInterface, close() | experimental (dostarczony silnik) | 3.1.0 |
CursorInterface | interface | writeContent(string), setFont(string, string, float), writeText(float, float, string), finalizePage() | experimental (dostarczony silnik) | 3.1.0 |
open() zgłasza InvalidArgumentException dla strumienia bez możliwości zapisu oraz LogicException, jeśli zapis jest już otwarty. close() nie jest idempotentne. Drugie wywołanie zgłasza wyjątek.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”<?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.}Funkcja jest typowana względem interfejsu experimental, więc pozostaje odseparowana od klasy silnika. Core wstrzykuje działającą implementację w miejscu wywołania.
Przykład kodu — produkcja
Dział zatytułowany „Przykład kodu — produkcja”<?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 gwarantuje, że zapis zostanie zamknięty, a zwiastun zapisany, nawet gdy pętla stron zgłosi wyjątek. Strumień nadal należy do wywołującego i to on go zamyka.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Opieraj kod na interfejsie, a nie na klasie silnika. Silnik implementujący oba kontrakty jest wewnętrzny i nie stanowi części powierzchni publicznej. Nie wywołuj na nim
newani nie odwołuj się do niego po nazwie. Użyj podpowiedzi typuStreamingWriterInterfacei pozwól, by Core dostarczył implementację. - Kontrakt jest
experimental. Jego sygnatura może się zmienić w wydaniu pomocniczym, po uprzednim powiadomieniu o wycofaniu. Ustaw ścisłe ograniczenie wersji albo opakuj go we własny adapter, zanim uzależnisz od niego środowisko produkcyjne. - Kursor staje się nieważny w chwili wywołania kolejnego
newPage()lubclose(). Przechowywanie nieaktualnego kursora i wywołanie jego metody zgłaszaLogicException. Dla jasności finalizuj jawnie. close()nie jest idempotentne. Drugie wywołanie to błąd wywołującego, a nie stan możliwy do odzyskania. Kontrakt zgłasza wyjątek.- Zapis nigdy nie zamyka strumienia. Jeśli po zwróceniu wartości przez
close()nie zamkniesz uchwytu należącego do wywołującego, nastąpi wyciek deskryptora pliku. - Silnik opróżnia każdą sfinalizowaną stronę, więc pamięć rezydentna nie rośnie wraz z liczbą stron. Dokładny profil pamięci należy do charakterystyki poziomu
experimentali może się zmieniać między wydaniami pomocniczymi. Nie utrwalaj założenia opartego na pojedynczym pomiarze.
Wydajność
Dział zatytułowany „Wydajność”Projekt mechanizmu strumieniowego ogranicza szczytowe zużycie pamięci. Dostarczony silnik opróżnia każdą ukończoną stronę i zwalnia jej bufor, więc pamięć rezydentna nie rośnie wraz z liczbą stron, w przeciwieństwie do zapisu w pamięci. Silnik zrzuca własną ewidencję odsyłaczy oraz drzewa stron do tymczasowych strumieni opartych na dysku, aby utrzymać zużycie pamięci procesu na poziomie zbliżonym do stałego. Konkretne wartości pamięci i czasu zegarowego należą do charakterystyki poziomu experimental i mogą się zmieniać między wydaniami pomocniczymi, więc ta strona nie podaje żadnej stałej liczby. performance_budget wynoszący 1500 ms czasu zegarowego i 64 MB szczytu to obwiednia kanwy, a nie umowna gwarancja. Powtarzalność jest bitwise: ta sama treść i konfiguracja dają wyjście identyczne co do bajtu, co utrwalają testy złotej linii bazowej silnika.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”Metoda kursora writeContent() to niskopoziomowa furtka. Dołącza dostarczone bajty dosłownie do strumienia treści strony i nie weryfikuje składni ani semantyki operatorów. Niezaufane dane przekazane do writeContent() tworzą uszkodzony lub złośliwy plik PDF. Traktuj tę metodę jako powierzchnię przyjmującą wyłącznie zaufane dane, a dla dowolnego tekstu przygotowywanego przez wywołującego preferuj writeText(). Dostarczony kursor stosuje sekwencje ucieczki dla tekstu przekazanego do writeText() zgodnie z gramatyką literałów łańcuchowych PDF, ale nie oczyszcza surowych operatorów. Model, w którym strumień należy do wywołującego, również ma znaczenie dla bezpieczeństwa. Silnik pisze do strumienia, ale nigdy go nie zamyka ani nie otwiera ponownie, więc nie może przekierować wyjścia. Środowiskowa powierzchnia ataku pozostaje realna, ponieważ silnik jest dostarczany. Wywołujący nigdy nie mogą przekazywać niezaufanych bajtów do writeContent(), a silnik musi przestrzegać niezmienników kontraktu.
Zgodność
Dział zatytułowany „Zgodność”| Twierdzenie | Standard | Klauzula | Dowód |
|---|---|---|---|
| Strumień treści koduje zawartość strony jako sekwencję operatorów graficznych, które kursor dołącza. | ISO 32000-2 | §8 | |
| Zapis emituje strukturę odsyłaczy odwzorowującą każdy numer obiektu na jego przesunięcie bajtowe przy zamykaniu. | ISO 32000-2 | §7 |
Obie klauzule są przypięte w glosariuszu i ujęte parafrazą. NextPDF nie odtwarza żadnego tekstu normatywnego. ADR zapisu strumieniowego, do którego odwołuje się PHPDoc kontraktu, zawiera uzasadnienie cyklu życia oraz zakresu.
Kontekst komercyjny
Dział zatytułowany „Kontekst komercyjny”Przetestowany silnik strumieniowy jest dostarczany w otwartoźródłowym Core pod tymi kontraktami experimental. Klasy silnika są wewnętrzne, więc korzystasz ze strumieniowania przez publiczny kontrakt, a nie przez nazwę konkretnej klasy. NextPDF Pro i NextPDF Enterprise stosują ten sam kontrakt, więc kod napisany pod StreamingWriterInterface w Core pozostaje ważny wobec implementacji Premium tego samego kontraktu. Zastrzeżeniem jest poziom experimental, a nie edycja czy dostępność. Sygnatura może się zmienić w wydaniu pomocniczym po uprzednim powiadomieniu o wycofaniu.
Zobacz także
Dział zatytułowany „Zobacz także”- Kontrakty: 41 publicznych interfejsów (SPI) opisuje przegląd SPI oraz poziomy stabilności.
- Kontrakty / Document opisuje zapis w pamięci, który te kontrakty uzupełniają.
- Writer opisuje obiekt PDF oraz emiter odsyłaczy.
- HTML / Ograniczenia strumieniowania (ADR-001) opisuje uzasadnienie zakresu strumieniowania.
- Wydajność opisuje uzasadnienie wyjścia strumieniowego ograniczającego zużycie pamięci.