Contracts / Streaming
In een oogopslag
Sectie met titel “In een oogopslag”Het streaming-domein bevat twee experimental-interfaces: StreamingWriterInterface voor incrementele PDF-uitvoer en CursorInterface voor het samenstellen van inhoud op paginaniveau. Core levert een geteste final engine die beide implementeert. De engineklassen zijn intern, dus u gebruikt het publieke experimental-contract in plaats van de engine zelf te implementeren. Omdat het niveau experimental is, kan het contract in een minor release wijzigen met een voorafgaande deprecatiemelding. Pin de versie strikt of verpak het contract achter uw eigen adapter voordat u er in productie van afhankelijk wordt.
Installatie
Sectie met titel “Installatie”composer require nextpdf/core:^3Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”Een streaming-writer serialiseert elke pagina terwijl u die samenstelt en kan die naar de uitvoer doorspoelen voordat de volgende pagina begint. Gebruik deze wanneer een document het beschikbare geheugenbudget kan overschrijden. De in-memory writer houdt het hele document vast. Een streaming-writer doet dat niet. StreamingWriterInterface definieert een strikte toestandsmachine. Een nieuwe instantie begint in CLOSED. open() verplaatst de instantie naar OPEN en schrijft de PDF-header naar een door de aanroeper aangeleverde stream. newPage() verplaatst de instantie naar PAGING en retourneert een cursor. close() schrijft de kruisverwijzingsstructuur en de trailer, en verplaatst de instantie vervolgens naar de terminale toestand CLOSED. Een kruisverwijzingsstream koppelt elk objectnummer aan zijn byte-offset, zoals beschreven in ISO 32000-2 §7. Per instantie loopt slechts één sessie. Na close() is de instantie verbruikt. De aanroeper is eigenaar van de streamresource. De writer schrijft ernaar, maar sluit die nooit.
CursorInterface is het schrijfoppervlak op paginaniveau. U verkrijgt een cursor via StreamingWriterInterface::newPage(), en die blijft geldig totdat u die finaliseert, totdat de volgende newPage() die automatisch finaliseert, of totdat close() die ongeldig maakt. Ongeldigmaking is permanent. Een cursor kan niet opnieuw worden geactiveerd. Elke methode op een ongeldig gemaakte cursor gooit LogicException. De cursor schrijft ruwe content-stream-operatoren, stelt het actieve lettertype in en schrijft gepositioneerde tekst. Een content stream codeert pagina-inhoud als een reeks grafische operatoren, zoals beschreven in ISO 32000-2 §8. De cursor is een schrijfoppervlak op laag niveau: die voert geen text shaping, bidirectionele herordening, regelafbreking of lay-out uit. Dat blijven concerns op Document-niveau. De single-cursor-invariant geldt overal: er is op elk moment hooguit één cursor geldig.
Beide interfaces zijn experimental, en Core levert er een werkende engine achter: een finale StreamingWriterInterface-implementatie, de bijbehorende paginacursor en een discard sink die wordt gebruikt voor geheugenbenchmarking. Deze engineklassen zijn intern en maken geen deel uit van het publieke oppervlak. Om streaming te gebruiken, vertrouwt u op het experimental-contract en laat u Core de implementatie leveren. De PHPDoc op elk type verwijst naar de streaming-writer-ADR voor de toestandsmachine van de levenscyclus en de scope-rationale. Omdat het niveau experimental is, kan de contractsignatuur nog steeds wijzigen in een minor release met een voorafgaande deprecatiemelding. Pin de versie strikt of verpak het contract achter uw eigen adapter voordat u er in productie van afhankelijk wordt.
API-oppervlak
Sectie met titel “API-oppervlak”| Type | Soort | Belangrijke leden | Stabiliteit | Sinds |
|---|---|---|---|---|
StreamingWriterInterface | interface | open(resource, Config), newPage(?PageSize): CursorInterface, close() | experimental (geleverde engine) | 3.1.0 |
CursorInterface | interface | writeContent(string), setFont(string, string, float), writeText(float, float, string), finalizePage() | experimental (geleverde engine) | 3.1.0 |
open() gooit InvalidArgumentException bij een niet-schrijfbare stream en LogicException als de writer al open is. close() is niet idempotent. Een tweede aanroep gooit.
Codevoorbeeld — Snelstart
Sectie met titel “Codevoorbeeld — Snelstart”<?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.}De functie is gericht op de experimental-interface en blijft daardoor losgekoppeld van de engineklasse. Core injecteert een werkende implementatie op de plek van de aanroep.
Codevoorbeeld — Productie
Sectie met titel “Codevoorbeeld — Productie”<?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(); } }}Het finally-blok garandeert dat de writer wordt gesloten en de trailer wordt geschreven, zelfs wanneer een paginalus gooit. De aanroeper blijft eigenaar van de stream en sluit die zelf.
Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”- Vertrouw op de interface, niet op de engineklasse. De engine die beide contracten implementeert, is intern en maakt geen deel uit van het publieke oppervlak. Instantieer die niet met
newen verwijs er niet bij naam naar. Type-hintStreamingWriterInterfaceen laat Core de implementatie leveren. - Het contract is
experimental. De signatuur kan in een minor release wijzigen, met een voorafgaande deprecatiemelding. Pin de versie strikt of verpak het contract achter uw eigen adapter voordat u er in productie van afhankelijk wordt. - Een cursor wordt ongeldig op het moment dat de volgende
newPage()ofclose()wordt aangeroepen. Een verouderde cursor vasthouden en er een methode op aanroepen gooitLogicException. Finaliseer expliciet voor de duidelijkheid. close()is niet idempotent. Twee keer aanroepen is een fout van de aanroeper, geen herstelbare situatie. Het contract gooit.- De writer sluit de stream nooit. Als u vergeet een door de aanroeper bezeten handle te sluiten nadat
close()terugkeert, lekt u een bestandsdescriptor. - De engine spoelt elke gefinaliseerde pagina door, zodat het resident geheugen niet groeit met het aantal pagina’s. Het exacte geheugenprofiel is een eigenschap van het
experimental-niveau en kan tussen minor releases verschuiven. Hardcodeer geen aanname op basis van één meting.
Prestaties
Sectie met titel “Prestaties”Het streaming-ontwerp begrenst het piekgeheugen. De geleverde engine spoelt elke voltooide pagina door en geeft de buffer ervan vrij, zodat de resident set niet groeit met het aantal pagina’s, in tegenstelling tot de in-memory writer. De engine schrijft de administratie voor kruisverwijzing en paginaboom weg naar schijfgebaseerde tijdelijke streams om de procesvoetafdruk vrijwel constant te houden. Concrete geheugen- en wandkloktijdcijfers zijn een eigenschap van het experimental-niveau en kunnen tussen minor releases wijzigen, dus deze pagina legt geen vast getal vast. Het performance_budget van 1500 ms wandkloktijd en 64 MB piek is de canvas-enveloppe, geen contractuele garantie. Reproduceerbaarheid is bitwise: dezelfde inhoud en configuratie produceren byte-identieke uitvoer, wat wordt vastgepind door de golden-baseline-tests van de engine.
Beveiligingsopmerkingen
Sectie met titel “Beveiligingsopmerkingen”De writeContent()-methode van de cursor is een low-level escape hatch. Die voegt de aangeleverde bytes letterlijk toe aan de content stream van de pagina en valideert geen operatorsyntaxis of -semantiek. Niet-vertrouwde invoer die wordt doorgegeven aan writeContent() produceert een corrupte of kwaadaardige PDF. Behandel die methode als een API-oppervlak dat uitsluitend vertrouwde invoer accepteert, en geef de voorkeur aan writeText() voor alle tekst die door de aanroeper wordt beïnvloed. De geleverde cursor escapet tekst die wordt doorgegeven aan writeText() voor de PDF-literal-string-grammatica, maar saneert geen ruwe operatoren. Het model waarbij de aanroeper eigenaar is van de stream is eveneens een beveiligingseigenschap. De engine schrijft naar de stream, maar sluit of heropent die nooit, zodat de engine de uitvoer niet kan omleiden. Het runtime-aanvalsoppervlak is reëel omdat de engine wordt geleverd. Aanroepers mogen nooit niet-vertrouwde bytes aan writeContent() voeden, en de engine moet de invarianten van het contract respecteren.
Conformiteit
Sectie met titel “Conformiteit”| Claim | Standaard | Clausule | Bewijs |
|---|---|---|---|
| Een content stream codeert pagina-inhoud als een reeks grafische operatoren, die de cursor toevoegt. | ISO 32000-2 | §8 | |
| De writer zendt een kruisverwijzingsstructuur uit die elk objectnummer aan zijn byte-offset koppelt bij het sluiten. | ISO 32000-2 | §7 |
Beide clausules zijn glossary-pinned en geparafraseerd. NextPDF reproduceert geen normatieve tekst. De streaming-writer-ADR waarnaar de contract-PHPDoc verwijst, bevat de levenscyclus en scope-rationale.
Commerciële context
Sectie met titel “Commerciële context”Een geteste streaming-engine wordt geleverd in de open-source Core achter deze experimental-contracten. De engineklassen zijn intern, dus u gebruikt streaming via het publieke contract in plaats van een concrete klassenaam. NextPDF Pro en NextPDF Enterprise volgen hetzelfde contract, dus code die is geschreven tegen StreamingWriterInterface in Core, blijft geldig tegen een Premium-implementatie van hetzelfde contract. Het experimental-niveau, niet de editie of beschikbaarheid, is het voorbehoud. De signatuur kan in een minor release wijzigen met een voorafgaande deprecatiemelding.
Zie ook
Sectie met titel “Zie ook”- Contracts: 41 publieke interfaces (SPI) behandelt het SPI-overzicht en de stabiliteitsniveaus.
- Contracts / Document behandelt de in-memory writer die door deze contracten wordt aangevuld.
- Writer behandelt het PDF-object en de kruisverwijzingsemitter.
- HTML / Streaming constraints (ADR-001) behandelt de scope-rationale van streaming.
- Performance behandelt de geheugenrationale voor streaming-uitvoer.