Pro Edition
Stream — Ausführliche Referenz
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Diese Seite dokumentiert die öffentlichen Verträge, Klassen, Methoden und Fehlermodi des Subsystems NextPDF\Pro\Stream über die Überblicksseite hinaus. Jeder Typ unten ist Teil der dokumentierten öffentlichen Pro-Oberfläche.
Verfügbarkeit & Lizenzierung
Abschnitt betitelt „Verfügbarkeit & Lizenzierung“Diese Fähigkeit wird in NextPDF Pro (nextpdf/pro) ausgeliefert und aktiviert sich mit einem Lizenz-Envelope der Pro-Stufe. Eine Bereitstellung ohne diese Berechtigung lädt die Klassen der Fähigkeit nicht. Editionen vergleichen und Lizenz erwerben.
Kein per-Feature-Lizenz-Flag gilt; der Code wird mit der Pro-Edition ausgeliefert. Worker-Anzahl, Batch-Größe, Wiederholungsbudget und Store-Backend sind Laufzeitparameter.
Architektur: die eingefrorene Engine-Naht
Abschnitt betitelt „Architektur: die eingefrorene Engine-Naht“NextPDF\Pro\Stream\Engine\RenderEngineInterface ist der Vertrag zwischen der Durchsatz-Engine und dem Document-Job-Stream-Prozessor. Die Engine implementiert ihn (besitzt Nebenläufigkeit, Worker-Pool-Lifecycle, Backpressure, beschränkten Speicher); der Stream-Prozessor konsumiert ihn (besitzt Keyed State, Dedup, Retry, Checkpoint und Exactly-once-Commit). Die Engine gibt Bytes plus sha-256 zurück, niemals einen committeten Ort — genau diese Nebenwirkungsfreiheit erlaubt es dem Prozessor, exactly-once zu stagen, zu committen und zu checkpointen.
public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input orderpublic function maxBatchSize(): int; // int<1, max> backpressure hintpublic function isAvailable(): bool;$manifests ist eine list<RenderManifest> der Größe von höchstens maxBatchSize(); $variablesByJobId bildet eine Job-ID auf array<string, scalar>-Template-Variablen ab. Ein Fehler pro Manifest ist ein Failed/Timeout-Ergebnis pro Item und bricht den Batch niemals ab.
Engines
Abschnitt betitelt „Engines“NextPDF\Pro\Stream\Engine\InProcessRenderEngine
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\InProcessRenderEngine“Synchrone Single-Process-Basis. Validiert jedes Manifest fail-closed über RenderManifestValidator (16-MiB-Inline-Payload-Obergrenze, Conformance-/Signatur-Allow-Lists, sha-256-Content-Hash-Format, BCP-47-Locale-Syntax), bevor sie über den Core-SingleDocumentRenderer rendert. Ein blockierender Validierungsfehler nimmt einen Kurzschluss zu EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...); eine Render-Ausnahme wird zu 'SPEC-RENDER-EXCEPTION'. Konstruktor: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null) — maxBatchSize < 1 löst eine InvalidArgumentException aus. isAvailable() ist immer true.
NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine“final readonly, __construct(RenderUnitExecutorInterface $executor). Umhüllt jedes Manifest in eine indizierte RenderUnit, lässt sie durch den Executor laufen und sortiert die Abschlüsse nach Index neu, sodass die Ausgabe byte-gleich zu einem sequentiellen Render ist. Ein Abschluss-Index außerhalb von [0, count) löst RenderEngineException::unknownUnit() aus; ein wiederholter Index löst duplicateResult() aus; ein fehlender Index löst missingResult() aus. maxBatchSize() und isAvailable() delegieren an den Executor.
Executors
Abschnitt betitelt „Executors“NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface“public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any orderpublic function maxBatchSize(): int;public function isAvailable(): bool;Implementierungen dürfen Abschlüsse in beliebiger Reihenfolge liefern; ConcurrentRenderEngine stellt die Reihenfolge nach Index wieder her.
NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor“final readonly, __construct(RenderEngineInterface $inner). Rendert jede Unit der Reihe nach über die innere Engine — die deterministische Korrektheitsreferenz, der ein paralleler Executor byte-für-byte entsprechen muss. Keine Zeit, keine Prozesse, keine Threads, keine Zufälligkeit.
NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor“final readonly. Verteilt einen Batch auf bis zu maxWorkers php-Worker-Subprozesse (je ein Chunk), die parallel rendern; die Ausgabe ist byte-gleich zur Inline-Basis. Konstruktor:
__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)Robustheitsvertrag:
- Deadlock-frei, Windows-sicher. Unit-Payloads und -Ergebnisse reisen über temporäre Dateien, nicht über Pipes; der Parent pollt
proc_get_status()und leert eine Pipe erst bis EOF, nachdem ein Worker beendet wurde, sodass ein Worker den Parent nicht festklemmen kann. - Beschränkte Wartezeit.
timeoutSecondsbegrenzt das gesamte parallele Rendern; bei Ablauf wird jeder noch laufende Worker beendet und eineRenderEngineExceptionausgelöst. - Ressourcenhygiene. Ein
finallyschließt Pipes, unternimmt einen beschränkten Terminate-and-Reap-Versuch bei überlebenden Workern (graceful terminate → force-kill → reap; ein Child, das innerhalb der beschränkten Schonfrist nicht als gestoppt beobachtet wird, wird aufgegeben, statt eine unbestimmte Blockade zu riskieren) und entfernt jede temporäre Datei auf allen Pfaden. - Vertrauenswürdige Korrelation. Jeder Worker muss genau seine zugewiesene Index-Menge zurückgeben (kein fehlender, doppelter oder fremder Index); die Bytes jedes gerenderten Ergebnisses werden neu gehasht und gegen den vom Worker gemeldeten sha-256 abgeglichen, und jeder Status außer
rendered/failedführt zu einem harten Fehler. Ein Render-Fehler pro Manifest ist einFailed-Ergebnis pro Unit; nur eine infrastrukturelle Störung (Exit ungleich null, unlesbare/fehlerhafte Ausgabe, Timeout) führt zu einem harten Fehler des Executors.
isAvailable() erfordert, dass sowohl die Autoload-Datei als auch das Worker-Skript existieren. Eine nicht-positive Schranke oder eine negative Timeout-Angabe löst eine InvalidArgumentException aus.
Render-Units und Ergebnisse
Abschnitt betitelt „Render-Units und Ergebnisse“NextPDF\Pro\Stream\Engine\RenderUnit
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\RenderUnit“final readonly — int<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. Die Korrelation erfolgt über index, niemals über die Job-ID (Job-IDs sind innerhalb eines Batches nicht garantiert eindeutig).
NextPDF\Pro\Stream\Engine\CompletedRenderUnit
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\CompletedRenderUnit“final readonly — int $index (nicht vertrauenswürdig, von der Engine validiert), EngineRenderResult $result.
NextPDF\Pro\Stream\Engine\EngineRenderResult
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\EngineRenderResult“final readonly. Felder: 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() meldet den Status. Ein gerendertes Ergebnis trägt Bytes und einen Digest, niemals einen committeten Ort.
NextPDF\Pro\Stream\Engine\EngineRenderStatus
Abschnitt betitelt „NextPDF\Pro\Stream\Engine\EngineRenderStatus“String-backed Enum: Rendered, Failed, Timeout. isRetryable() ist true nur für Timeout, sodass der Aufrufer einen Timeout als transient klassifiziert, ohne den Fehler erneut zu inspizieren.
NextPDF\Pro\Stream\Commit\OutputCommitterInterface
Abschnitt betitelt „NextPDF\Pro\Stream\Commit\OutputCommitterInterface“public function commit( string $jobId, OutputObjectKey $target, string $bytes, string $sha256, bool $overwrite = false,): CommitReceipt;Exactly-once-Veröffentlichung: atomar, idempotent (ein byte-gleiches Re-Commit führt keinen Schreibvorgang durch und gibt ein CommitReceipt mit idempotentReuse = true zurück — eine frische Quittung, nicht das Original; ihr committedAt ist die aktuelle Uhrzeit), ohne stilles Überschreiben und integritätsgeprüft (der Committer berechnet den Digest neu). Fehlermodi: CommitIntegrityException (der deklarierte sha-256 stimmt nicht mit den Bytes überein), OutputCommitConflictException (abweichende Bytes an einem belegten Schlüssel mit overwrite = false), UnsupportedTargetException (nicht unterstütztes Zielschema).
NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter
Abschnitt betitelt „NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter“final readonly, implementiert OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Bedient nur das file-Schema; löst jedes Ziel unter einer konfigurierten Wurzel auf und schreibt über einen atomaren Writer (O_EXCL temp → fsync → Rename auf demselben Volume). Der vollständige kritische Abschnitt (einschließlich der Erstellung des übergeordneten Verzeichnisses) läuft unter einem exklusiven flock auf einer Lock-Datei pro Wurzel, die außerhalb des Ausgabe-Keyspace gehalten wird, und der Commit ist fail-closed, wenn das Lock nicht geöffnet oder erlangt werden kann. Er verweigert symlink-Endkomponenten und jeden Schlüssel, der einen Doppelpunkt enthält (NTFS-Alternate-Data-Stream-Vektor). Cross-Host-nebenläufiges Exactly-once auf denselben Schlüssel erfordert den dauerhaften Enterprise-Committer. Eine Wurzel, die das System-Temp-Verzeichnis ist oder enthält, löst eine InvalidArgumentException aus.
NextPDF\Pro\Stream\Commit\CommitReceipt
Abschnitt betitelt „NextPDF\Pro\Stream\Commit\CommitReceipt“final readonly — jobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() sind vollständig round-trip-fähig (das Ziel ist strukturiert, keine verlustbehaftete URI); fromArray() ist strikt und löst bei fehlenden oder fehlerhaften Feldern eine InvalidArgumentException aus.
Checkpoint
Abschnitt betitelt „Checkpoint“NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface
Abschnitt betitelt „NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface“load(string $runId): ?RunCheckpoint und save(RunCheckpoint $checkpoint): void (dauerhaft und atomar — ein Leser sieht niemals einen halb geschriebenen Checkpoint).
NextPDF\Pro\Stream\Checkpoint\RunCheckpoint
Abschnitt betitelt „NextPDF\Pro\Stream\Checkpoint\RunCheckpoint“final readonly — runId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0'. Factories start(runId, at) und advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() serialisieren ihn; fromArray() erfordert eine nicht-leere Run-ID und ein gültiges updated_at, weist eine inkompatible (nicht-1.x-)schema_version zurück und normalisiert den Keyed State, indem es alle nicht-JSON-serialisierbaren Werte in jeder Tiefe verwirft, sodass der wiederhergestellte Zustand stets re-serialisierbar ist. Bei der Wiederherstellung spult der Prozessor über committedOffset hinaus vor und stellt den Keyed State wieder her; ein Zustand, der nach der letzten Barriere mutiert wurde, wird vorwärts neu berechnet, niemals ein Fehler, weil dauerhaftes Exactly-once aus der Digest-Deduplizierung des Committers stammt.
NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore
Abschnitt betitelt „NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore“final readonly, implementiert CheckpointStoreInterface, DurableCapability. Eine JSON-Datei pro Lauf, atomar geschrieben. Run-IDs müssen [A-Za-z0-9._-]+ entsprechen und kein .. enthalten; ein nicht existierendes Verzeichnis löst eine InvalidArgumentException aus.
Idempotenz-Deduplizierung
Abschnitt betitelt „Idempotenz-Deduplizierung“NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface
Abschnitt betitelt „NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface“isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Der schnelle Pfad, der vor dem Rendern eines Replays einen Kurzschluss nimmt; der Digest-Vergleich des Committers bleibt die dauerhafte Garantie, sodass ein verlorener Eintrag schlimmstenfalls ein erneutes Rendern verschwendet, das der Committer dedupliziert.
InMemoryIdempotencyStore— Single-Run-/Testumfang (bei Absturz verloren).FilesystemIdempotencyStore—DurableCapability; eine atomare JSON-Datei pro committetem Schlüssel (die serialisierte Quittung), benannt nach einem Hash des Schlüsselwerts. Marks sind idempotent; ein nebenläufiges Re-Mark läuft harmlos auf einer Datei in ein Race. Ein nicht existierendes Verzeichnis löst eineInvalidArgumentExceptionaus.
Retry und Dead-Letters
Abschnitt betitelt „Retry und Dead-Letters“NextPDF\Pro\Stream\Retry\RetryPolicy
Abschnitt betitelt „NextPDF\Pro\Stream\Retry\RetryPolicy“final readonly — positive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs. __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000) mit den Invarianten maxAttempts >= 1 und 1 <= baseDelayMs <= maxDelayMs <= 7 days (sonst InvalidArgumentException). Factories default() und none() (einzelner Versuch). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> ist ein deterministisches exponentielles Backoff baseDelayMs * 2^(attempt-1), gedeckelt bei maxDelayMs (kein eingebauter Jitter; an der Aufrufstelle anwenden).
NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface
Abschnitt betitelt „NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface“add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>.
NextPDF\Pro\Stream\Retry\DeadLetterRecord
Abschnitt betitelt „NextPDF\Pro\Stream\Retry\DeadLetterRecord“final readonly — jobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, optional ?string $runId, optional int<1, max> $sourceOffset. dedupKey() ist runId:sourceOffset, wenn beide bekannt sind, andernfalls der Idempotenz-Schlüsselwert. fromArray() parst failed_at strikt als ATOM (relative oder nicht-ATOM-Ausdrücke werden zurückgewiesen), sodass Serialisieren/Deserialisieren symmetrisch bleibt.
InMemoryDeadLetterStore— Single-Run-/Testumfang.FilesystemDeadLetterStore—DurableCapability; eine atomare JSON-Datei pro Record, benannt nach einem SHA-256-Hash des Dedup-Schlüssels (….dlq.json), sodass das erneute Hinzufügen desselben Items beim Resume idempotent ist.all()liest Records in deterministischer (sortierter) Reihenfolge und legt einen korrupten Record offen, indem es eine Ausnahme auslöst;count()ist eine günstige Dateizählung, keine Gültigkeitsprüfung.
Keyed State
Abschnitt betitelt „Keyed State“NextPDF\Pro\Stream\State\KeyedStateStoreInterface
Abschnitt betitelt „NextPDF\Pro\Stream\State\KeyedStateStoreInterface“has, get, put, remove, clear sowie snapshot(): array und restore(array $snapshot): void für die Checkpoint-Grenze. Werte müssen JSON-serialisierbar sein. Für die standardmäßige Render-and-Commit-Last wird kein Keyed State verwendet; er existiert für Aggregations-/Windowing-Erweiterungen. InMemoryKeyedStateStore ist die Single-Run-Implementierung; ihn bei der Wiederherstellung zu verlieren ist für die Standardlast ein semantischer No-op, weil Exactly-once aus der Digest-Deduplizierung des Committers stammt.
NextPDF\Pro\Stream\State\KeySelector
Abschnitt betitelt „NextPDF\Pro\Stream\State\KeySelector“final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string leitet den Partitionsschlüssel aus den Manifest-Metadaten als rawurlencode(tenant):rawurlencode(document) ab (die Kodierung verhindert, dass ("a:b","c") mit ("a","b:c") kollidiert) und fällt auf die Job-ID zurück, wenn eines der Felder fehlt — sodass jedes Manifest auf einen stabilen, nicht-leeren Schlüssel auflöst.
Durability-Marker und Ausnahmen
Abschnitt betitelt „Durability-Marker und Ausnahmen“NextPDF\Pro\Stream\DurableCapability ist ein Marker-Interface für jeden Store/Committer, dessen Zustand einen Prozess-Neustart überlebt. Ein absturzsicherer Lauf erfordert, dass jeder Kollaborateur ihn implementiert, sodass er schnell scheitert, statt ein Exactly-once zu versprechen, das ein in-memory-Store nicht halten kann.
Alle Subsystem-Ausnahmen implementieren NextPDF\Pro\Stream\Exception\StreamException (erweitert Throwable), sodass ein Aufrufer einheitlich catch (StreamException) kann:
RenderEngineException(RuntimeException) — der Executor hat den Batch-Vertrag verletzt (unbekannte, doppelte oder fehlende Unit; Worker-Störung; Timeout).CommitIntegrityException(RuntimeException) — der deklarierte sha-256 stimmt nicht mit dem Payload überein; Spec-CodeSPEC-COMMIT-422.OutputCommitConflictException(RuntimeException) — abweichende Bytes an einem belegten Schlüssel mit deaktiviertem Overwrite; Spec-CodeSPEC-COMMIT-409(überspecCode()exponiert).UnsupportedTargetException(InvalidArgumentException) — Zielschema, das ein Committer nicht bedienen kann.
Konformität
Abschnitt betitelt „Konformität“Die Engine validiert Manifeste gegen das Core-Manifest-Modell und erzeugt deterministische Bytes plus sha-256-Digests; der Committer erzwingt atomare, integritätsgeprüfte Exactly-once-Schreibvorgänge. Das Modul führt keine kryptografischen Operationen über sha-256-Content-Digests hinaus durch und definiert kein FIPS-spezifisches Verhalten.
Sonderfälle & Fallstricke
Abschnitt betitelt „Sonderfälle & Fallstricke“renderBatch()bricht bei einem Fehler pro Manifest niemals ab; inspizieren Sie jedesEngineRenderResult.ProcessPoolRenderUnitExecutorkorreliert strikt über den Index und hasht die Worker-Bytes neu; ein fehlerhafter Worker führt zu einem harten Fehler, statt die Ausgabe zu beschädigen.LocalFilesystemCommitterist Single-Host; Cross-Host-Exactly-once erfordert den dauerhaften Enterprise-Committer.- Absturzsichere Läufe müssen durchgängig die
DurableCapability-(Dateisystem-)Stores verwenden, nicht die in-memory-Varianten.
Veröffentlichungsgrenze
Abschnitt betitelt „Veröffentlichungsgrenze“Diese Seite dokumentiert ausschließlich extern beobachtbares Verhalten und die unterstützte öffentliche API-Oberfläche. Interne Namespace-Pfade, Hilfsklassen, Mechanismustabellen, Runbook-Dateinamen und Ticket-Präfixe liegen außerhalb des Geltungsbereichs.