Skip to content

Contracts: 41 public interfaces (SPI)

NextPDF\Contracts is the public service-provider interface (SPI): 41 interfaces and enums under src/Contracts/ with an explicit @stability tag and a backward-compatibility promise. Extension packages, framework bridges, and the Pro and Enterprise editions depend on these types, never concrete classes.

Terminal window
composer require nextpdf/core:^3

The engine exposes two surfaces. Concrete classes under src/Core/, src/Html/, and src/Writer/ have no compatibility promise and can change between minor versions. The Contracts namespace works differently. It is a curated set of types with signatures frozen for the stability tier they declare. Everything outside the engine depends on this namespace, and nothing deeper. That includes the Laravel, Symfony, and CodeIgniter bridges, the compat-tcpdf shim, NextPDF Server, and the Pro and Enterprise editions.

Each contract declares one of four tiers in its PHPDoc. A stable contract allows no breaking change in a minor or patch release. New methods arrive only with default implementations. An experimental contract may change in a minor release with a deprecation notice. A deprecated contract names its replacement. A small number are contract-only, such as StreamingWriterInterface and CursorInterface. The type is published and frozen, but no production implementation ships yet.

The authoritative tier list is docs/extension-points.json (manifest version 3.0.0, 67 published points across Contracts and Event). A machine-verifiable test, tests/Unit/Contracts/StabilityContractTest.php, reads that manifest and fails the build under five conditions. First, a listed type is missing. Second, a reflected kind disagrees with the manifest. Third, an @stability PHPDoc tag drifts from the manifest. Fourth, a contract under src/Contracts/ is absent from the manifest. Fifth, an @internal type leaks into it. The contract surface cannot drift undetected.

Contracts fall into nine domains, each covered by a dedicated page: document construction, signing, barcode encoding, typography, security policy, extraction, observability, and streaming. The split mirrors how you adopt the engine. You depend on the document contract to generate Portable Document Format (PDF) files. You depend on the signing contracts to add a signature. You depend on the security-policy contracts to constrain untrusted HTML.

Optional implementation resolution follows one pattern across the engine. Core checks for a concrete class with class_exists() and casts it to the contract. LtvManagerInterface and PdfAManagerInterface resolve their Pro implementations this way. Core stays Apache-2.0 with no hard dependency on commercial code.

ContractKindStabilitySinceDomain
PdfDocumentInterfaceinterfacestable1.0.0document
DocumentFactoryInterfaceinterfacestable1.7.0document
ResettableServiceinterfacestable1.7.0document
OutputDestinationenumstable1.0.0document
Orientationenumstable1.0.0document
Alignmentenumstable1.0.0document
SignerInterfaceinterfacestable1.0.0signing
HsmSignerInterfaceinterfacestable1.0.0signing
DeferredSignerInterfaceinterfaceexperimental3.0.0signing
TimestampProviderInterfaceinterfaceexperimental3.0.0signing
LtvManagerInterfaceinterfacestable1.10.0signing
CryptoPolicyInterfaceinterfacestable1.9.0signing
Barcode1DEncoderInterfaceinterfacestable1.0.0barcode
Barcode2DEncoderInterfaceinterfacestable1.0.0barcode
BarcodeEncoderInterfaceinterfacestable3.0.0barcode
Gs1DataParserInterfaceinterfacestable1.0.0barcode
FontRegistryInterfaceinterfacestable1.7.0typography
TextPreprocessorInterfaceinterfacestable1.9.0typography
HtmlSecurityPolicyInterfaceinterfacestable3.1.0security-policy
ExternalResourcePolicyInterfaceinterfacestable4.0.0security-policy
InspectorInterfaceinterfaceexperimental2.2.0extraction
EmbeddingServiceInterfaceinterfaceexperimental2.1.0extraction
VectorIndexInterfaceinterfaceexperimental2.1.0extraction
JobNotificationInterfaceinterfaceexperimental2.2.0observability
SpectrumInterfaceinterfaceexperimental2.1.0observability
StreamingWriterInterfaceinterfaceexperimental3.1.0streaming
CursorInterfaceinterfaceexperimental3.1.0streaming

The table lists the primary contracts. The remaining types, including value-object data transfer objects (DTOs) (TextSegment, TextPreprocessResult), the EInvoice sub-namespace, behavior enums (DegradationPolicy, UnderlineStyle), and import contracts (ImportedFormObjectInterface, EmbeddedPdfObjectInterface, ChromeRenderResultInterface), are documented on the domain pages under See also. The full machine-readable list is docs/extension-points.json, mirrored to .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() returns a concrete Document that satisfies PdfDocumentInterface. Type-hint the interface in your services so engine internals stay swappable.

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 implements DocumentFactoryInterface. It holds process-lifetime FontRegistryInterface and ImageRegistryInterface singletons and injects them into each disposable Document, so a worker parses each font once across thousands of requests.

  • A contract-only type compiles but has no runtime backing. A new expression against StreamingWriterInterface or CursorInterface cannot succeed because no class implements them yet. Treat them as a forward-declared application programming interface (API).
  • The @stability PHPDoc tag is the source of truth for a single type. docs/extension-points.json is the source of truth for the set. When they disagree, StabilityContractTest fails. Do not hide the disagreement by editing one side.
  • experimental does not mean unstable in practice. It means the compatibility promise is weaker. Read each contract’s bc_promise field in .ai/contracts-map.md before pinning to it.
  • An @internal class is never a contract even if other packages can technically reference it. The stability test rejects any @internal type that appears in the manifest.
  • Adding a method to a stable interface is a breaking change for implementers unless the method ships with a default implementation. The engine adds capability through new interfaces, not by widening existing ones.

Programming against Contracts adds no measurable runtime cost: an interface type-hint resolves at link time, not per call. The performance_budget for this page’s worker example is 1500 ms wall and 64 MB peak across three documents. Font parsing on the first request dominates that budget. Later requests reuse the registry cache and drop to single-digit milliseconds of contract-attributable work. The cost model is O(1) per contract dispatch; the work is in the concrete implementation, documented on each domain page.

The SPI is also a security boundary. HtmlSecurityPolicyInterface and ExternalResourcePolicyInterface are deny-by-default contracts that constrain what untrusted HTML can do before it reaches a renderer. CryptoPolicyInterface gates algorithm and key-strength selection for signing and encryption. Because these are contracts, you can supply a stricter policy without forking the engine. Pin to the stable tier for any security-relevant policy. Experimental policy contracts may change shape between minor releases. The signing and security-policy domain pages carry the full threat model and normative references.

This overview makes no direct normative claim; each domain page renders its own citations block. The signing contracts map to ISO 32000-2 §12.8 (digital signatures) and ETSI EN 319 142 (PAdES baselines). The PDF/A manager maps to ISO 19005-4. See the signing, security-policy, and extraction pages for clause-level conformance tables.

The Pro and Enterprise editions provide the production code behind several core contracts: LtvManagerInterface (long-term validation), PdfAManagerInterface (PDF/A enforcement), the Hardware Security Module (HSM) and deferred signers, the barcode encoders, and the embedding and vector-index contracts. Core publishes and freezes the interface; the Premium package ships the implementation. This keeps the open-source engine Apache-2.0 and gives commercial deployments a drop-in upgrade with no API change.