Pro édition
Stream — Référence approfondie
Cette page documente les contrats publics, classes, méthodes et modes de défaillance du sous-système NextPDF\Pro\Stream au-delà de la page de présentation. Chaque type ci-dessous fait partie de la surface publique Pro documentée.
Disponibilité et licence
Section intitulée « Disponibilité et licence »Cette capacité est fournie dans NextPDF Pro (nextpdf/pro) et s’active avec une enveloppe de licence de niveau Pro. Un déploiement sans ce droit ne charge pas les classes de la capacité. Compare les éditions et obtiens une licence.
Aucun indicateur de licence par fonctionnalité ne s’applique ; le code est fourni avec l’édition Pro. Le nombre de workers, la taille de lot, le budget de nouvelles tentatives et le backend de store sont des paramètres au runtime.
Architecture : la jointure de moteur figée
Section intitulée « Architecture : la jointure de moteur figée »NextPDF\Pro\Stream\Engine\RenderEngineInterface est le contrat entre le moteur de débit et le processor de flux de jobs documentaires. Le moteur l’implémente (détenant la concurrence, le cycle de vie du pool de workers, la contre-pression, la mémoire bornée) ; le processor de flux le consomme (détenant l’état clavé, la déduplication, les nouvelles tentatives, le jalonnement et le commit exactement-une-fois). Le moteur renvoie des octets plus une sha-256, jamais un emplacement commité — cette absence d’effet de bord est ce qui permet au processor de mettre en attente, de commiter et de jalonner exactement une fois.
public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input orderpublic function maxBatchSize(): int; // int<1, max> backpressure hintpublic function isAvailable(): bool;$manifests est une list<RenderManifest> de taille au plus maxBatchSize() ; $variablesByJobId associe un identifiant de job à des variables de template array<string, scalar>. Un échec par manifeste est un résultat Failed/Timeout par élément et n’interrompt jamais le lot.
NextPDF\Pro\Stream\Engine\InProcessRenderEngine
Section intitulée « NextPDF\Pro\Stream\Engine\InProcessRenderEngine »Référence synchrone, mono-processus. Valide chaque manifeste de façon verrouillée via RenderManifestValidator (plafond de charge utile en ligne de 16 MiB, listes d’autorisation conformité/signature, format de hachage de contenu sha-256, syntaxe de locale BCP-47) avant de rendre via le SingleDocumentRenderer du Core. Une erreur de validation bloquante court-circuite vers EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...) ; une exception de rendu devient 'SPEC-RENDER-EXCEPTION'. Constructeur : __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null) — maxBatchSize < 1 lève InvalidArgumentException. isAvailable() est toujours true.
NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine
Section intitulée « NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine »final readonly, __construct(RenderUnitExecutorInterface $executor). Enveloppe chaque manifeste dans une RenderUnit indexée, les exécute via l’exécuteur et retrie les achèvements par index, de sorte que la sortie soit identique octet pour octet à un rendu séquentiel. Un index d’achèvement hors de [0, count) lève RenderEngineException::unknownUnit() ; un index répété lève duplicateResult() ; un index manquant lève missingResult(). maxBatchSize() et isAvailable() délèguent à l’exécuteur.
Exécuteurs
Section intitulée « Exécuteurs »NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface
Section intitulée « NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface »public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any orderpublic function maxBatchSize(): int;public function isAvailable(): bool;Les implémentations peuvent produire les achèvements dans n’importe quel ordre ; ConcurrentRenderEngine rétablit l’ordre par index.
NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor
Section intitulée « NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor »final readonly, __construct(RenderEngineInterface $inner). Rend chaque unité dans l’ordre via le moteur interne — la référence de correction déterministe qu’un exécuteur parallèle doit reproduire octet pour octet. Pas de temps, de processus, de threads ni d’aléa.
NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor
Section intitulée « NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor »final readonly. Répartit un lot sur jusqu’à maxWorkers sous-processus worker php (un fragment chacun) qui rendent en parallèle ; la sortie est identique octet pour octet à la référence inline. Constructeur :
__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)Contrat de robustesse :
- Sans interblocage, sûr sous Windows. Les charges utiles et les résultats d’unités transitent par des fichiers temporaires, pas par des pipes ; le parent interroge
proc_get_status()et ne draine un pipe jusqu’à EOF qu’après la sortie d’un worker, de sorte qu’un worker ne puisse pas bloquer le parent. - Attente bornée.
timeoutSecondsplafonne tout le rendu parallèle ; à l’expiration, chaque worker encore en cours est terminé et uneRenderEngineExceptionest levée. - Hygiène des ressources. Un
finallyferme les pipes, effectue une tentative bornée de terminaison-et-récolte sur les workers survivants (terminaison gracieuse → arrêt forcé → récolte ; un enfant dont l’arrêt n’est pas observé dans le délai de grâce borné est abandonné plutôt que de risquer un blocage indéfini) et supprime chaque fichier temporaire sur tous les chemins. - Corrélation de confiance. Chaque worker doit renvoyer exactement son jeu d’index assigné (aucun index manquant, dupliqué ni étranger) ; les octets de chaque résultat rendu sont re-hachés et comparés à la sha-256 rapportée par le worker, et tout statut autre que
rendered/failedéchoue durement. Un échec de rendu par manifeste est un résultatFailedpar unité ; seule une faute d’infrastructure (sortie non nulle, sortie illisible/corrompue, timeout) fait échouer durement l’exécuteur.
isAvailable() exige l’existence à la fois du fichier d’autoload et du script worker. Une borne non positive ou un timeout négatif lève InvalidArgumentException.
Unités de rendu et résultats
Section intitulée « Unités de rendu et résultats »NextPDF\Pro\Stream\Engine\RenderUnit
Section intitulée « NextPDF\Pro\Stream\Engine\RenderUnit »final readonly — int<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. La corrélation se fait par index, jamais par identifiant de job (les identifiants de job ne sont pas garantis uniques au sein d’un lot).
NextPDF\Pro\Stream\Engine\CompletedRenderUnit
Section intitulée « NextPDF\Pro\Stream\Engine\CompletedRenderUnit »final readonly — int $index (non fiable, validé par le moteur), EngineRenderResult $result.
NextPDF\Pro\Stream\Engine\EngineRenderResult
Section intitulée « NextPDF\Pro\Stream\Engine\EngineRenderResult »final readonly. Champs : jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings. Fabriques : rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (code SPEC-ENGINE-TIMEOUT). isRendered() rapporte le statut. Un résultat rendu porte des octets et une empreinte, jamais un emplacement commité.
NextPDF\Pro\Stream\Engine\EngineRenderStatus
Section intitulée « NextPDF\Pro\Stream\Engine\EngineRenderStatus »Énumération adossée à une chaîne : Rendered, Failed, Timeout. isRetryable() est true uniquement pour Timeout, de sorte que l’appelant classe un timeout comme transitoire sans réinspecter l’erreur.
NextPDF\Pro\Stream\Commit\OutputCommitterInterface
Section intitulée « NextPDF\Pro\Stream\Commit\OutputCommitterInterface »public function commit( string $jobId, OutputObjectKey $target, string $bytes, string $sha256, bool $overwrite = false,): CommitReceipt;Publication exactement-une-fois : atomique, idempotente (un re-commit identique octet pour octet n’effectue aucune écriture et renvoie un CommitReceipt avec idempotentReuse = true — un reçu neuf, pas l’original ; son committedAt est l’horloge courante), sans écrasement silencieux et vérifiée en intégrité (le committer recalcule l’empreinte). Modes de défaillance : CommitIntegrityException (la sha-256 déclarée ne correspond pas aux octets), OutputCommitConflictException (octets divergents sur une clé occupée avec overwrite = false), UnsupportedTargetException (schéma de cible non pris en charge).
NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter
Section intitulée « NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter »final readonly, implémente OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Ne dessert que le schéma file ; résout chaque cible sous une unique racine configurée et écrit via un writer atomique (temp O_EXCL → fsync → rename sur le même volume). Toute la section critique (y compris la création du répertoire parent) s’exécute sous un flock exclusif sur un fichier de verrou par racine conservé en dehors de l’espace de clés de sortie, et le commit est verrouillé en cas d’échec si le verrou ne peut être ouvert ou acquis. Il refuse les composants finaux en lien symbolique et toute clé contenant un deux-points (vecteur de flux de données alternatif NTFS). Le commit exactement-une-fois concurrent inter-hôtes vers la même clé nécessite le committer Enterprise durable. Une racine qui est ou contient le répertoire temporaire système lève InvalidArgumentException.
NextPDF\Pro\Stream\Commit\CommitReceipt
Section intitulée « NextPDF\Pro\Stream\Commit\CommitReceipt »final readonly — jobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() sont entièrement réversibles (la cible est structurée, pas un URI avec perte) ; fromArray() est stricte et lève InvalidArgumentException sur des champs manquants ou mal formés.
NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface
Section intitulée « NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface »load(string $runId): ?RunCheckpoint et save(RunCheckpoint $checkpoint): void (durable et atomique — un lecteur ne voit jamais un jalon à demi écrit).
NextPDF\Pro\Stream\Checkpoint\RunCheckpoint
Section intitulée « NextPDF\Pro\Stream\Checkpoint\RunCheckpoint »final readonly — runId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt ; SCHEMA_VERSION = '1.0'. Fabriques start(runId, at) et advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() le sérialisent ; fromArray() exige un identifiant d’exécution non vide et un updated_at valide, rejette un schema_version incompatible (non-1.x) et normalise l’état clavé en écartant toute valeur non sérialisable en JSON à chaque profondeur, de sorte que l’état récupéré soit toujours re-sérialisable. À la reprise, le processor avance rapidement au-delà de committedOffset et restaure l’état clavé ; l’état muté après la dernière barrière est recalculé en avant, jamais une erreur, parce que l’exactement-une-fois durable provient de la déduplication par empreinte du committer.
NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore
Section intitulée « NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore »final readonly, implémente CheckpointStoreInterface, DurableCapability. Un fichier JSON par exécution, écrit de façon atomique. Les identifiants d’exécution doivent correspondre à [A-Za-z0-9._-]+ et ne contenir aucun .. ; un répertoire inexistant lève InvalidArgumentException.
Déduplication par idempotence
Section intitulée « Déduplication par idempotence »NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface
Section intitulée « NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface »isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Le chemin rapide qui court-circuite avant de rendre une rediffusion ; la comparaison d’empreinte du committer reste la garantie durable, si bien qu’un enregistrement perdu gaspille au pire un re-rendu que le committer déduplique.
InMemoryIdempotencyStore— portée mono-exécution / test (perdu en cas de panne).FilesystemIdempotencyStore—DurableCapability; un fichier JSON atomique par clé commitée (le reçu sérialisé), nommé par un hachage de la valeur de la clé. Les marques sont idempotentes ; une re-marque concurrente entre en concurrence sans dommage sur un seul fichier. Un répertoire inexistant lèveInvalidArgumentException.
Nouvelles tentatives et lettres mortes
Section intitulée « Nouvelles tentatives et lettres mortes »NextPDF\Pro\Stream\Retry\RetryPolicy
Section intitulée « 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) avec les invariants maxAttempts >= 1 et 1 <= baseDelayMs <= maxDelayMs <= 7 days (sinon InvalidArgumentException). Fabriques default() et none() (un seul essai). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> est un backoff exponentiel déterministe baseDelayMs * 2^(attempt-1) plafonné à maxDelayMs (pas de jitter intégré ; applique-le au point d’appel).
NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface
Section intitulée « NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface »add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>.
NextPDF\Pro\Stream\Retry\DeadLetterRecord
Section intitulée « NextPDF\Pro\Stream\Retry\DeadLetterRecord »final readonly — jobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, optionnel ?string $runId, optionnel int<1, max> $sourceOffset. dedupKey() est runId:sourceOffset lorsque les deux sont connus, sinon la valeur de la clé d’idempotence. fromArray() analyse failed_at strictement comme ATOM (rejetant les expressions relatives ou non-ATOM), de sorte que sérialisation/désérialisation reste symétrique.
InMemoryDeadLetterStore— portée mono-exécution / test.FilesystemDeadLetterStore—DurableCapability; un fichier JSON atomique par enregistrement, nommé par un hachage SHA-256 de la clé de déduplication (….dlq.json), de sorte que ré-ajouter le même élément à la reprise soit idempotent.all()lit les enregistrements dans un ordre déterministe (trié) et fait remonter un enregistrement corrompu en levant une exception ;count()est un décompte de fichiers bon marché, pas une vérification de validité.
État clavé
Section intitulée « État clavé »NextPDF\Pro\Stream\State\KeyedStateStoreInterface
Section intitulée « NextPDF\Pro\Stream\State\KeyedStateStoreInterface »has, get, put, remove, clear, plus snapshot(): array et restore(array $snapshot): void pour la frontière de jalon. Les valeurs doivent être sérialisables en JSON. Pour la charge de travail par défaut de rendu-et-commit, aucun état clavé n’est utilisé ; il existe pour les extensions d’agrégation/fenêtrage. InMemoryKeyedStateStore est l’implémentation mono-exécution ; le perdre à la reprise est un no-op sémantique pour la charge de travail par défaut, parce que l’exactement-une-fois provient de la déduplication par empreinte du committer.
NextPDF\Pro\Stream\State\KeySelector
Section intitulée « NextPDF\Pro\Stream\State\KeySelector »final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string dérive la clé de partition à partir des métadonnées de manifeste sous la forme rawurlencode(tenant):rawurlencode(document) (l’encodage empêche ("a:b","c") d’entrer en collision avec ("a","b:c")), en se rabattant sur l’identifiant de job lorsque l’un des champs est absent — de sorte que chaque manifeste se résolve en une clé stable et non vide.
Marqueur de durabilité et exceptions
Section intitulée « Marqueur de durabilité et exceptions »NextPDF\Pro\Stream\DurableCapability est une interface marqueur pour tout store/committer dont l’état survit à un redémarrage de processus. Une exécution résistante aux pannes exige que chaque collaborateur l’implémente, de sorte qu’elle échoue rapidement au lieu de promettre une exactement-une-fois qu’un store en mémoire ne peut tenir.
Toutes les exceptions du sous-système implémentent NextPDF\Pro\Stream\Exception\StreamException (étend Throwable), de sorte qu’un appelant puisse catch (StreamException) de façon uniforme :
RenderEngineException(RuntimeException) — l’exécuteur a violé le contrat de lot (unité inconnue, dupliquée ou manquante ; faute de worker ; timeout).CommitIntegrityException(RuntimeException) — la sha-256 déclarée ne correspond pas à la charge utile ; code specSPEC-COMMIT-422.OutputCommitConflictException(RuntimeException) — octets divergents sur une clé occupée avec écrasement désactivé ; code specSPEC-COMMIT-409(exposé viaspecCode()).UnsupportedTargetException(InvalidArgumentException) — schéma de cible qu’un committer ne peut desservir.
Conformité
Section intitulée « Conformité »Le moteur valide les manifestes par rapport au modèle de manifeste du Core et produit des octets déterministes plus des empreintes sha-256 ; le committer impose des écritures atomiques, vérifiées en intégrité et exactement-une-fois. Le module n’effectue aucune opération cryptographique au-delà des empreintes de contenu sha-256 et ne définit aucun comportement spécifique à FIPS.
Cas limites et pièges
Section intitulée « Cas limites et pièges »renderBatch()n’interrompt jamais sur un échec par manifeste ; inspecte chaqueEngineRenderResult.ProcessPoolRenderUnitExecutorcorrèle strictement par index et re-hache les octets du worker ; un worker bogué échoue durement plutôt que de corrompre la sortie.LocalFilesystemCommitterest mono-hôte ; l’exactement-une-fois inter-hôtes nécessite le committer Enterprise durable.- Les exécutions résistantes aux pannes doivent utiliser de bout en bout les stores
DurableCapability(système de fichiers), pas les variantes en mémoire.
Frontière de publication
Section intitulée « Frontière de publication »Cette page documente uniquement le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins de namespace internes, les classes d’aide, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de tickets sont hors périmètre.