Ga naar inhoud
getnextpdf.com

Pro editie

Stream — Diepe referentie

Deze pagina documenteert de publieke contracten, klassen, methoden en faalmodi van het NextPDF\Pro\Stream-subsysteem die verder gaan dan de overzichtspagina. Elk type hieronder maakt deel uit van het gedocumenteerde publieke Pro-oppervlak.

Deze mogelijkheid wordt geleverd in NextPDF Pro (nextpdf/pro) en wordt geactiveerd met een licentie-envelope op Pro-niveau. Een deployment zonder die rechten laadt de klassen van de mogelijkheid niet. Vergelijk edities en vraag een licentie aan.

Er geldt geen licentievlag per feature; de code wordt geleverd met de Pro-editie. Aantal workers, batchgrootte, retry-budget en store-backend zijn runtimeparameters.

NextPDF\Pro\Stream\Engine\RenderEngineInterface is het contract tussen de throughput-engine en de document-job-streamprocessor. De engine implementeert het (en bezit concurrency, worker-pool-levenscyclus, backpressure, begrensd geheugen); de streamprocessor consumeert het (en bezit keyed state, dedup, retry, checkpoint en exactly-once commit). De engine retourneert bytes plus sha-256, nooit een gecommitte locatie — die side-effect-vrijheid is wat de processor in staat stelt om exactly once te stagen, committen en checkpointen.

public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input order
public function maxBatchSize(): int; // int<1, max> backpressure hint
public function isAvailable(): bool;

$manifests is een list<RenderManifest> met een grootte van ten hoogste maxBatchSize(); $variablesByJobId mapt job-id op array<string, scalar> template-variabelen. Een mislukking per manifest is een Failed/Timeout-resultaat per item en breekt de batch nooit af.

NextPDF\Pro\Stream\Engine\InProcessRenderEngine

Sectie met titel “NextPDF\Pro\Stream\Engine\InProcessRenderEngine”

Synchrone, single-process-baseline. Valideert elk manifest fail-closed via RenderManifestValidator (16 MiB inline-payload-cap, conformiteits-/handtekening-allow-lists, sha-256-content-hash-formaat, BCP-47-localesyntaxis) voordat het rendert via de Core-SingleDocumentRenderer. Een blokkerende validatiefout sluit kort naar EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...); een render-exception wordt 'SPEC-RENDER-EXCEPTION'. Constructor: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)maxBatchSize < 1 werpt InvalidArgumentException. isAvailable() is altijd true.

NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine

Sectie met titel “NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine”

final readonly, __construct(RenderUnitExecutorInterface $executor). Verpakt elk manifest in een geïndexeerde RenderUnit, draait ze door de executor en sorteert de voltooiingen opnieuw op index zodat de uitvoer byte-identiek is aan een sequentiële render. Een voltooiingsindex buiten [0, count) werpt RenderEngineException::unknownUnit(); een herhaalde index werpt duplicateResult(); een ontbrekende index werpt missingResult(). maxBatchSize() en isAvailable() delegeren naar de executor.

NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface

Sectie met titel “NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface”
public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any order
public function maxBatchSize(): int;
public function isAvailable(): bool;

Implementaties mogen voltooiingen in elke volgorde yielden; ConcurrentRenderEngine herstelt de volgorde op index.

NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor

Sectie met titel “NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor”

final readonly, __construct(RenderEngineInterface $inner). Rendert elke unit in volgorde via de inner engine — de deterministische correctheidsreferentie die een parallelle executor byte-voor-byte moet evenaren. Geen tijd, processen, threads of randomness.

NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor

Sectie met titel “NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor”

final readonly. Verdeelt een batch over maximaal maxWorkers php-workersubprocessen (elk één chunk) die parallel renderen; de uitvoer is byte-identiek aan de inline-baseline. Constructor:

__construct(
int $maxWorkers = 4,
int $maxBatchSize = 64,
?string $phpBinary = null,
?string $workerScript = null,
?string $autoload = null,
?int $timeoutSeconds = 300, // null disables the wall-clock watchdog
)

Robuustheidscontract:

  • Deadlock-vrij, Windows-veilig. Unit-payloads en -resultaten reizen via temp-bestanden, niet via pipes; de parent pollt proc_get_status() en draineert een pipe pas tot EOF nadat een worker is afgesloten, zodat een worker de parent niet kan vastzetten.
  • Begrensde wachttijd. timeoutSeconds begrenst de hele parallelle render; bij verloop wordt elke nog draaiende worker beëindigd en wordt een RenderEngineException geworpen.
  • Resource-hygiëne. Een finally sluit pipes, doet een begrensde terminate-and-reap-poging op overlevende workers (graceful terminate → force-kill → reap; een child die niet binnen de begrensde grace tot stilstand wordt waargenomen, wordt achtergelaten in plaats van een onbepaalde blokkering te riskeren), en unlinkt elk temp-bestand op alle paden.
  • Vertrouwde correlatie. Elke worker moet exact zijn toegewezen indexset retourneren (geen ontbrekende, dubbele of vreemde index); de bytes van elk gerenderd resultaat worden opnieuw gehasht en gematcht tegen de door de worker gerapporteerde sha-256, en elke andere status dan rendered/failed faalt hard. Een render-mislukking per manifest is een Failed-resultaat per unit; alleen een infrastructurele fout (niet-nul exit, onleesbare/verminkte uitvoer, timeout) laat de executor hard falen.

isAvailable() vereist dat zowel het autoload-bestand als het worker-script bestaan. Een niet-positieve grens of negatieve timeout werpt InvalidArgumentException.

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. Correlatie verloopt via index, nooit via job-id (job-id’s zijn niet gegarandeerd uniek binnen een batch).

final readonlyint $index (untrusted, gevalideerd door de engine), EngineRenderResult $result.

final readonly. Velden: jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings. Factories: rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (code SPEC-ENGINE-TIMEOUT). isRendered() rapporteert de status. Een gerenderd resultaat draagt bytes en een digest, nooit een gecommitte locatie.

String-backed enum: Rendered, Failed, Timeout. isRetryable() is true alleen voor Timeout, zodat de caller een timeout als transiënt classificeert zonder de fout opnieuw te inspecteren.

NextPDF\Pro\Stream\Commit\OutputCommitterInterface

Sectie met titel “NextPDF\Pro\Stream\Commit\OutputCommitterInterface”
public function commit(
string $jobId,
OutputObjectKey $target,
string $bytes,
string $sha256,
bool $overwrite = false,
): CommitReceipt;

Exactly-once-publicatie: atomair, idempotent (een byte-identieke re-commit voert geen schrijfactie uit en retourneert een CommitReceipt met idempotentReuse = true — een verse receipt, niet de originele; zijn committedAt is de huidige klok), geen stille overschrijving en integriteitsgecontroleerd (de committer herberekent de digest). Faalmodi: CommitIntegrityException (de gedeclareerde sha-256 komt niet overeen met de bytes), OutputCommitConflictException (afwijkende bytes op een bezette sleutel met overwrite = false), UnsupportedTargetException (niet-ondersteund target-schema).

NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter

Sectie met titel “NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter”

final readonly, implementeert OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Bedient alleen het file-schema; lost elk target op onder één geconfigureerde root en schrijft via een atomaire writer (O_EXCL temp → fsync → same-volume rename). De volledige kritieke sectie (inclusief het aanmaken van de parent-directory) draait onder een exclusieve flock op een per-root-lockbestand dat buiten de output-keyspace wordt gehouden, en de commit is fail-closed als de lock niet kan worden geopend of verkregen. Het weigert symlinked finale componenten en elke sleutel die een dubbele punt bevat (NTFS alternate-data-stream-vector). Cross-host concurrente exactly-once naar dezelfde sleutel vereist de duurzame Enterprise-committer. Een root die de systeem-temp-directory is of bevat, werpt InvalidArgumentException.

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() zijn volledig round-trippable (het target is gestructureerd, geen lossy URI); fromArray() is strikt en werpt InvalidArgumentException bij ontbrekende of misvormde velden.

NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface

Sectie met titel “NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface”

load(string $runId): ?RunCheckpoint en save(RunCheckpoint $checkpoint): void (duurzaam en atomair — een reader ziet nooit een half geschreven checkpoint).

final readonlyrunId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0'. Factories start(runId, at) en advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() serialiseren het; fromArray() vereist een niet-lege run-id en een geldige updated_at, wijst een incompatibele (niet-1.x) schema_version af, en normaliseert keyed state door op elke diepte alle niet-JSON-serialiseerbare waarden te laten vallen zodat herstelde state altijd herserialiseerbaar is. Bij herstel spoelt de processor vooruit voorbij committedOffset en herstelt het de keyed state; state die na de laatste barrière is gemuteerd, wordt voorwaarts herberekend, nooit een fout, omdat duurzame exactly-once afkomstig is van de digest-dedup van de committer.

NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore

Sectie met titel “NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore”

final readonly, implementeert CheckpointStoreInterface, DurableCapability. Eén JSON-bestand per run, atomair geschreven. Run-id’s moeten overeenkomen met [A-Za-z0-9._-]+ en geen .. bevatten; een niet-bestaande directory werpt InvalidArgumentException.

NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface

Sectie met titel “NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface”

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Het snelle pad dat kortsluit voordat een replay wordt gerenderd; de digest-vergelijking van de committer blijft de duurzame garantie, zodat een verloren record in het slechtste geval een herrender verspilt die de committer dedupliceert.

  • InMemoryIdempotencyStore — single-run / testscope (verloren bij crash).
  • FilesystemIdempotencyStoreDurableCapability; één atomair JSON-bestand per gecommitte sleutel (de geserialiseerde receipt), benoemd naar een hash van de sleutelwaarde. Marks zijn idempotent; een concurrente re-mark racet onschadelijk op één bestand. Een niet-bestaande directory werpt InvalidArgumentException.

final readonlypositive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs. __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000) met invarianten maxAttempts >= 1 en 1 <= baseDelayMs <= maxDelayMs <= 7 days (anders InvalidArgumentException). Factories default() en none() (enkele poging). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> is deterministische exponentiële backoff baseDelayMs * 2^(attempt-1) begrensd op maxDelayMs (geen ingebouwde jitter; pas die toe op de call-site).

NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface

Sectie met titel “NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface”

add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>.

final readonlyjobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, optioneel ?string $runId, optioneel int<1, max> $sourceOffset. dedupKey() is runId:sourceOffset wanneer beide bekend zijn, anders de idempotentie-sleutelwaarde. fromArray() parseert failed_at strikt als ATOM (en wijst relatieve of niet-ATOM-expressies af) zodat serialiseren/deserialiseren symmetrisch blijft.

  • InMemoryDeadLetterStore — single-run / testscope.
  • FilesystemDeadLetterStoreDurableCapability; één atomair JSON-bestand per record, benoemd naar een SHA-256-hash van de dedup-sleutel (….dlq.json), zodat het opnieuw toevoegen van hetzelfde item bij hervatting idempotent is. all() leest records in deterministische (gesorteerde) volgorde en brengt een corrupt record aan het licht door te werpen; count() is een goedkope bestandstelling, geen geldigheidscontrole.

NextPDF\Pro\Stream\State\KeyedStateStoreInterface

Sectie met titel “NextPDF\Pro\Stream\State\KeyedStateStoreInterface”

has, get, put, remove, clear, plus snapshot(): array en restore(array $snapshot): void voor de checkpointgrens. Waarden moeten JSON-serialiseerbaar zijn. Voor de standaard render-en-commit-workload wordt geen keyed state gebruikt; die bestaat voor aggregatie-/windowing-extensies. InMemoryKeyedStateStore is de single-run-implementatie; deze verliezen bij herstel is een semantische no-op voor de standaardworkload omdat exactly-once afkomstig is van de digest-dedup van de committer.

final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string leidt de partitiesleutel af uit manifestmetadata als rawurlencode(tenant):rawurlencode(document) (de encoding voorkomt dat ("a:b","c") botst met ("a","b:c")), met de job-id als fallback wanneer een van beide velden ontbreekt — zodat elk manifest oplost naar een stabiele, niet-lege sleutel.

NextPDF\Pro\Stream\DurableCapability is een marker-interface voor elke store/committer waarvan de state een procesherstart overleeft. Een crashveilige run vereist dat elke collaborator het implementeert, zodat hij fail-fast werkt in plaats van exactly-once te beloven die een in-memory-store niet kan nakomen.

Alle subsysteem-exceptions implementeren NextPDF\Pro\Stream\Exception\StreamException (extends Throwable), zodat een caller uniform catch (StreamException) kan gebruiken:

  • RenderEngineException (RuntimeException) — de executor heeft het batchcontract geschonden (onbekende, dubbele of ontbrekende unit; worker-fout; timeout).
  • CommitIntegrityException (RuntimeException) — de gedeclareerde sha-256 komt niet overeen met de payload; spec-code SPEC-COMMIT-422.
  • OutputCommitConflictException (RuntimeException) — afwijkende bytes op een bezette sleutel met overwrite uitgeschakeld; spec-code SPEC-COMMIT-409 (blootgesteld via specCode()).
  • UnsupportedTargetException (InvalidArgumentException) — een target-schema dat een committer niet kan bedienen.

De engine valideert manifesten tegen het Core-manifestmodel en produceert deterministische bytes plus sha-256-digests; de committer dwingt atomaire, integriteitsgecontroleerde, exactly-once-schrijfacties af. De module voert geen cryptografische bewerkingen uit buiten sha-256-content-digests en definieert geen FIPS-specifiek gedrag.

  • renderBatch() breekt nooit af bij een mislukking per manifest; inspecteer elk EngineRenderResult.
  • ProcessPoolRenderUnitExecutor correleert strikt op index en hasht worker-bytes opnieuw; een buggy worker faalt hard in plaats van de uitvoer te corrumperen.
  • LocalFilesystemCommitter is single-host; cross-host exactly-once vereist de duurzame Enterprise-committer.
  • Crashveilige runs moeten overal de DurableCapability- (bestandssysteem-)stores gebruiken, niet de in-memory-varianten.

Deze pagina documenteert alleen extern waarneembaar gedrag en het ondersteunde publieke API-oppervlak. Interne namespace-paden, helper-klassen, mechanismetabellen, runbook-bestandsnamen en ticket-prefixen vallen buiten de scope.