Aller au contenu

Contrats : 41 interfaces publiques (SPI)

NextPDF\Contracts est l’interface publique de fournisseur de services (SPI) : 41 interfaces et enums sous src/Contracts/ qui portent une balise @stability explicite et une promesse de rétrocompatibilité. Les packages d’extension, les ponts de framework et les éditions Pro et Enterprise s’appuient sur ces types, jamais sur des classes concrètes.

Fenêtre de terminal
composer require nextpdf/core:^3

Le moteur distingue deux surfaces. Les classes concrètes sous src/Core/, src/Html/ et src/Writer/ ne s’accompagnent d’aucune promesse de compatibilité. Elles peuvent changer librement d’une version mineure à l’autre. L’espace de noms Contracts fonctionne à l’inverse. C’est un ensemble soigneusement sélectionné de types dont les signatures sont figées au niveau de stabilité qu’ils déclarent. Tout ce qui se trouve hors du moteur dépend de cet espace de noms, et de rien de plus profond. Cela inclut les ponts Laravel, Symfony et CodeIgniter, le shim compat-tcpdf, NextPDF Server, ainsi que les éditions Pro et Enterprise.

Chaque contrat déclare l’un des quatre niveaux dans son PHPDoc. Un contrat stable ne permet aucune modification incompatible dans une version mineure ou correctrice. Les nouvelles méthodes n’y apparaissent qu’avec des implémentations par défaut. Un contrat experimental peut changer dans une version mineure, avec un avis de dépréciation. Un contrat deprecated nomme son remplaçant. Quelques-uns ne sont que des contrats, comme StreamingWriterInterface et CursorInterface. Le type est publié et figé, mais aucune implémentation de production n’est encore fournie.

La liste des niveaux qui fait autorité est docs/extension-points.json (manifeste version 3.0.0, 67 points publiés répartis entre Contracts et Event). Un test vérifiable automatiquement, tests/Unit/Contracts/StabilityContractTest.php, lit ce manifeste. Il fait échouer la build dans cinq cas. Le premier est un type listé qui manque. Le deuxième est une nature obtenue par réflexion qui contredit le manifeste. Le troisième est une balise PHPDoc @stability qui s’écarte du manifeste. Le quatrième est un contrat sous src/Contracts/ qui est absent du manifeste. Le cinquième est un type @internal qui y apparaît. La surface de contrat ne peut pas dériver sans être détectée.

Les contrats se répartissent en neuf domaines. Chacun a sa page dédiée : construction de documents, signature, encodage de codes-barres, typographie, politique de sécurité, extraction, observabilité et streaming. Cette répartition reflète la façon dont un intégrateur adopte le moteur. Tu t’appuies sur le contrat de document pour générer des PDF. Tu t’appuies sur les contrats de signature pour ajouter une signature. Tu t’appuies sur les contrats de politique de sécurité pour contraindre du HTML non fiable.

La résolution d’une implémentation optionnelle suit un seul et même schéma dans tout le moteur. Le cœur vérifie la présence d’une classe concrète avec class_exists() et la convertit vers le contrat. LtvManagerInterface et PdfAManagerInterface résolvent leurs implémentations Pro de cette manière. Le cœur reste donc Apache-2.0 sans dépendance dure envers du code commercial.

ContratNatureStabilitéDepuisDomaine
PdfDocumentInterfaceinterfacestable1.0.0document
DocumentFactoryInterfaceinterfacestable1.7.0document
ResettableServiceinterfacestable1.7.0document
OutputDestinationenumstable1.0.0document
Orientationenumstable1.0.0document
Alignmentenumstable1.0.0document
SignerInterfaceinterfacestable1.0.0signature
HsmSignerInterfaceinterfacestable1.0.0signature
DeferredSignerInterfaceinterfaceexperimental3.0.0signature
TimestampProviderInterfaceinterfaceexperimental3.0.0signature
LtvManagerInterfaceinterfacestable1.10.0signature
CryptoPolicyInterfaceinterfacestable1.9.0signature
Barcode1DEncoderInterfaceinterfacestable1.0.0code-barres
Barcode2DEncoderInterfaceinterfacestable1.0.0code-barres
BarcodeEncoderInterfaceinterfacestable3.0.0code-barres
Gs1DataParserInterfaceinterfacestable1.0.0code-barres
FontRegistryInterfaceinterfacestable1.7.0typographie
TextPreprocessorInterfaceinterfacestable1.9.0typographie
HtmlSecurityPolicyInterfaceinterfacestable3.1.0politique de sécurité
ExternalResourcePolicyInterfaceinterfacestable4.0.0politique de sécurité
InspectorInterfaceinterfaceexperimental2.2.0extraction
EmbeddingServiceInterfaceinterfaceexperimental2.1.0extraction
VectorIndexInterfaceinterfaceexperimental2.1.0extraction
JobNotificationInterfaceinterfaceexperimental2.2.0observabilité
SpectrumInterfaceinterfaceexperimental2.1.0observabilité
StreamingWriterInterfaceinterfaceexperimental3.1.0streaming
CursorInterfaceinterfaceexperimental3.1.0streaming

Le tableau liste les contrats principaux. Les autres types — les DTO objets-valeur (TextSegment, TextPreprocessResult), le sous-espace de noms EInvoice, les enums de comportement (DegradationPolicy, UnderlineStyle) et les contrats d’import (ImportedFormObjectInterface, EmbeddedPdfObjectInterface, ChromeRenderResultInterface) — sont documentés sur les pages de domaine sous Voir aussi. La liste complète et lisible par la machine est docs/extension-points.json, répliquée dans .ai/contracts-map.md.

examples/01-hello-world.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Hello World');
$doc->addPage();
$doc->setFont('helvetica', '', 24);
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$doc->save(__DIR__ . '/output/01-hello-world.pdf');

Document::createStandalone() renvoie un Document concret qui satisfait PdfDocumentInterface. Dans tes propres services, utilise l’interface comme type-hint afin que les éléments internes du moteur restent interchangeables.

examples/14-worker-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Core\PdfFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
// Created once at process boot in a RoadRunner/Swoole/Octane worker.
$fontRegistry = new FontRegistry();
$imageRegistry = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);
$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$factory = PdfFactory::new()
->withCompress(true)
->withDocumentFactory($documentFactory);
for ($request = 1; $request <= 3; $request++) {
$doc = $factory->create();
$doc->setTitle("Worker Request #{$request}");
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, "Worker Request #{$request}", newLine: true);
$doc->save(__DIR__ . "/output/14-worker-request-{$request}.pdf");
}

DocumentFactory implémente DocumentFactoryInterface. Elle conserve des singletons FontRegistryInterface et ImageRegistryInterface pendant toute la durée de vie du processus et les injecte dans chaque Document jetable, si bien qu’un worker analyse chaque police une seule fois sur des milliers de requêtes.

  • Un type qui n’existe que comme contrat compile, mais n’a aucun support à l’exécution. Un new contre StreamingWriterInterface ou CursorInterface ne peut pas réussir, car aucune classe ne les implémente encore. Traite-les comme une API déclarée à l’avance.
  • La balise PHPDoc @stability est la source de vérité pour un type donné. docs/extension-points.json est la source de vérité pour l’ensemble. Quand ils se contredisent, StabilityContractTest échoue — ne masque pas le désaccord en modifiant l’un des deux côtés.
  • experimental ne signifie pas instable en pratique ; cela veut dire que la promesse de compatibilité est plus faible. Lis le champ bc_promise de chaque contrat dans .ai/contracts-map.md avant de t’y lier.
  • Une classe @internal n’est jamais un contrat, même si d’autres packages peuvent techniquement la référencer. Le test de stabilité rejette tout type @internal qui apparaît dans le manifeste.
  • Ajouter une méthode à une interface stable est une modification incompatible pour ceux qui l’implémentent, sauf si la méthode est livrée avec une implémentation par défaut. Le moteur ajoute des capacités au moyen de nouvelles interfaces, pas en élargissant celles qui existent déjà.

Programmer contre Contracts n’ajoute aucun coût mesurable à l’exécution : un type-hint d’interface se résout au moment de l’édition de liens, pas à chaque appel. Le performance_budget de l’exemple de worker de cette page est de 1500 ms en temps réel et 64 Mo de pic sur trois documents. L’analyse des polices sur la première requête domine ce budget. Les requêtes suivantes réutilisent le cache du registre, avec un travail attribuable aux contrats qui descend à un nombre de millisecondes à un chiffre. Le modèle de coût est en O(1) par dispatch de contrat ; le travail réside dans l’implémentation concrète, documentée sur chaque page de domaine.

La SPI est aussi une frontière de sécurité. HtmlSecurityPolicyInterface et ExternalResourcePolicyInterface sont des contrats deny-by-default (refus par défaut) qui contraignent ce que du HTML non fiable peut faire avant d’atteindre un renderer. CryptoPolicyInterface contrôle le choix de l’algorithme et de la force de clé pour la signature et le chiffrement. Comme ce sont des contrats, un intégrateur peut fournir une politique plus stricte sans forker le moteur. Appuie-toi sur le niveau stable pour toute politique pertinente pour la sécurité. Les contrats de politique experimental peuvent changer de forme entre des versions mineures. Les pages de domaine de la signature et de la politique de sécurité présentent le modèle de menace complet et les références normatives.

Cette vue d’ensemble ne fait aucune affirmation normative directe ; chaque page de domaine fournit son propre bloc citations. Les contrats de signature correspondent à ISO 32000-2 §12.8 (signatures numériques) et ETSI EN 319 142 (références PAdES). Le gestionnaire PDF/A correspond à ISO 19005-4. Consulte les pages signature, politique de sécurité et extraction pour les tableaux de conformité au niveau des clauses.

Les éditions Pro et Enterprise implémentent le code de production derrière plusieurs contrats du cœur : LtvManagerInterface (validation à long terme), PdfAManagerInterface (application de PDF/A), les signataires Hardware Security Module (HSM) et différés, les encodeurs de codes-barres, ainsi que les contrats d’embedding et d’index vectoriel. Le cœur publie et fige l’interface ; le package Premium livre l’implémentation. Cela garde le moteur open-source Apache-2.0 tout en offrant aux déploiements commerciaux une mise à niveau immédiate sans changement d’API.