Ga naar inhoud

Een API die weigert te gokken

Spec: ISO/IEC 25010 Spec: ISO 32000-2 Evidence: Code-backed

NextPDF dwingt u expliciet te maken wat u bedoelt. Waar de intentie de bytes verandert — een handtekeningniveau, een uitvoerbestemming, een conformiteitsdoel — is dat een verplicht expliciet argument, niet iets wat de engine uit de context afleidt.

Deze pagina toont die houding in de eigen broncode van de engine: de methodesignaturen, de benoemde argumenten en de plekken waar dubbelzinnige invoer wordt geweigerd voordat ook maar één byte wordt geproduceerd.

Een gok is een beslissing die namens u wordt genomen zonder dat u dat merkt. Voor een tekstveld is dat licht ergerlijk. Voor een PDF is het een latent defect, want wat u oplevert is vaak een juridisch artefact of archiefartefact waarvan de juistheid later door iemand anders met een validator wordt gecontroleerd.

Neem een handtekening. De digest ervan wordt berekend over een gedeclareerd bytebereik dat de handtekeningwaarde zelf bewust uitsluit ( Spec: ISO 32000-2, §12.8 ). Een API die stilletjes “helpt” — structuur herschrijven, een niveau afleiden, een placeholder opvullen — heeft niet geholpen. Die heeft de bytes veranderd die een handtekening had moeten beschermen. De gok die op de aanroepplek vriendelijk lijkt, wordt weken later een productie-incident. Het is dezelfde regel code.

  • Als een keuze de uitvoer verandert en geen veilige standaardwaarde heeft, maakt NextPDF er een verplicht argument van, geen afgeleid argument.
  • Optionele argumenten die dubbelzinnig kunnen zijn, worden benoemd, zodat de aanroepplek de intentie uitdrukt (newLine: true, geen kale true).
  • Invoer die onveilig zou kunnen zijn, wordt gevalideerd vóór de weergave en geweigerd met een getypeerde uitzondering die de oorzaak benoemt.
  • Een documentinstantie is eenmalig bruikbaar: die wordt gebouwd, uitgevoerd en weggegooid. Er is geen reset(), dus is er geen “wordt deze instantie hergebruikt?” gokken.
  • De engine levert nooit een plausibel ogend artefact op in plaats van datgene waar u om hebt gevraagd. In plaats daarvan weigert hij.

Het mechanisme is eenvoudig, en dat is precies de bedoeling. Het draait om het typesysteem, benoemde argumenten, enums in plaats van magische strings, en een klein aantal bewuste guard clauses die vóór de uitvoer worden geplaatst.

De tabel zet een paar dubbelzinnige invoeren naast elkaar. Voor elk geval toont hij wat een bibliotheek die “helpt” zou afleiden, en wat NextPDF in plaats daarvan doet. De NextPDF-kolom beschrijft gedrag dat is geciteerd uit de broncode die verderop op deze pagina wordt getoond.

Dubbelzinnige invoerWat een gokkende bibliotheek doetWat NextPDF doet
Een oriëntatiestring zoals "portait"Valt terug op een standaardwaarde en geeft het toch weeraddPage() accepteert de Orientation-enum, geen string — een typefout is een typefout van het typesysteem, geen stille standaardwaarde
Een kale afsluitende true bij cell()Kiest de booleaanse positie waarvan hij aanneemt dat u die bedoeldeDe boolean wordt benoemd op de aanroepplek (newLine: true); een naamloze literal is de geurvlag die de API wegneemt
Een php://-wrapper of traversal-pad naar save()“Doet zijn best” en schrijft ergensGeweigerd voordat de PDF wordt gebouwd, met een getypeerde InvalidConfigException die sleutel, waarde en verwacht type benoemt
setSignature() en daarna save() terwijl de high-level ondertekenaar niet is aangeslotenLevert een niet-ondertekend bestand op dat de aanroeper voor ondertekend houdtWerpt NotImplementedException voordat er bytes worden geproduceerd, met vermelding van de ondersteunde route
Een Document-instantie hergebruiken voor een tweede weergaveGokt of resterende status nog van toepassing isGeen reset() en geen hergebruikspad — een verse instantie per verzoek via DocumentFactory, dus er is geen resterende status om over te gokken

Intentie is een verplicht argument. Het kerncontract, PdfDocumentInterface, accepteert geometrie en uitlijning als getypeerde value objects en enums, geen losse primitieven:

public function addPage(
?PageSize $size = null,
Orientation $orientation = Orientation::Portrait,
): static;
public function cell(
float $width,
float $height,
string $text = '',
bool|string $border = false,
bool $newLine = false,
Alignment $align = Alignment::Left,
bool $fill = false,
): static;

Orientation en Alignment zijn enums, dus de aanroep kan "portait" niet doorgeven en dat stilletjes “standaard” laten betekenen. Waar een standaardwaarde bestaat, is het een veilige (staand, links, geen rand), geen gok over wat u waarschijnlijk wilde.

Dubbelzinnige booleans worden benoemd op de aanroepplek. In de voorbeelden die als de feitelijke API-referentie dienen, komt steeds dezelfde vorm terug:

$document->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$document->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);
$pdf = $document->output(dest: OutputDestination::String);

newLine: true is onmiskenbaar. Een kale afsluitende true zou dat niet zijn. Het handtekeningniveau is SignatureLevel::PAdES_B_B, een enum-case — nooit een string die de engine moet interpreteren. De uitvoerbestemming is OutputDestination::String, zodat “geef me de bytes, geen HTTP-headers, geen bestand” expliciet wordt gemaakt. Het wordt niet afgeleid uit de vraag of er een bestandsnaam is meegegeven.

Onveilige invoer wordt geweigerd voordat er een byte wordt geschreven. save() valideert het bestemmingspad voordat het de PDF bouwt:

public function save(string $path): void
{
// Reject stream wrappers and null bytes
if (\str_contains($path, "\0") || \preg_match('#^[a-zA-Z]+://#', $path)) {
throw new InvalidConfigException(
configKey: 'output_path',
givenValue: $path,
expectedType: 'valid_path',
);
}
// Resolve the parent directory to prevent path traversal
$dir = \dirname($path);
$realDir = \realpath($dir);
if ($realDir === false) {
throw new InvalidConfigException(
configKey: 'output_path',
givenValue: $dir,
expectedType: 'existing_directory',
);
}
// ... only now is the PDF built and written atomically
}

De engine “doet niet zijn best” met een php://-wrapper of een traversal-pad. Hij weigert, en de uitzondering benoemt de sleutel, de waarde en wat werd verwacht.

De engine weigert liever dan een misleidend artefact op te leveren. De sterkste vorm van weigeren te gokken is helemaal geen uitvoer produceren wanneer die uitvoer onwaar zou zijn. Wanneer een high-level handtekening is geconfigureerd maar de writer-naad die daadwerkelijk zou ondertekenen niet is aangesloten, werpt het bouwpad een uitzondering voordat er bytes worden geproduceerd, in plaats van een niet-ondertekend bestand op te leveren dat de aanroeper voor ondertekend houdt:

if ($this->padesOrchestrator !== null) {
throw new NotImplementedException(
feature: 'Document::setSignature()->save()/output()/getPdfData()',
followUp: 'The high-level PAdES writer seam is not yet wired ... '
. 'Produce a signed PDF via the direct two-phase '
. 'PadesOrchestrator::signDocument() then finalizeSignature() '
. 'buffer API ...',
);
}

Een niet-ondertekende PDF die er ondertekend uitziet, is precies het soort plausibel ogend foutief artefact dat dit principe moet voorkomen. Dezelfde houding komt terug in het strikte CSS-pad. Een niet-geregistreerde spec-afwijking werpt een StrictModeViolation zodra die wordt gedetecteerd, in plaats van een benadering weer te geven en de afwijking onopgemerkt te laten.

Eenmalig gebruik schakelt een hele klasse gokken uit. Een Document is wegwerpbaar — gebouwd, uitgevoerd en weggegooid. Er is geen reset() en geen hergebruikspad. Een langdurig draaiende worker maakt per verzoek een verse instantie via DocumentFactory. De engine hoeft nooit te gokken of resterende status uit een vorig document nog betekenisvol is, omdat die er door de constructie niet is.

Deze pagina is Evidence: Code-backed : elke vorm van gedrag hierboven is geciteerd uit de eigen broncode van de engine en de bijbehorende voorbeelden, niet afgeleid uit de intentie.

  • De getypeerde signaturen met enums vormen het publieke contract in PdfDocumentInterface. De aanroepstijl met benoemde argumenten is de consistente vorm in de canonieke voorbeelden die als de feitelijke API-referentie fungeren.
  • De padvalidatie vóór de weergave, met de getypeerde InvalidConfigException, en de guard die vóór oplevering weigert met NotImplementedException zijn woordelijk geciteerd uit het uitvoerpad van de documentfaçade.
  • Het normenanker is Spec: ISO/IEC 25010, §3.32 — bescherming tegen gebruikersfouten, de kwaliteitseigenschap die een weiger-te-gokken-API op de aanroepplek moet waarborgen. Het tweede anker is Spec: ISO 32000-2, §12.8 , en daarom is gokken rond een ondertekend document nooit onschuldig. De digest omvat een gedeclareerd bytebereik dat de handtekeningwaarde uitsluit, dus elke stille herschrijving maakt die ongeldig.

Hieronder volgt een klein, volledig programma. Elke regel die dubbelzinnig zou kunnen zijn, drukt zijn intentie uit. De ene onveilige invoer wordt geweigerd voordat er enig werk wordt verricht.

<?php
declare(strict_types=1);
use NextPDF\Contracts\OutputDestination;
use NextPDF\Core\Document;
use NextPDF\Exception\InvalidConfigException;
use NextPDF\ValueObjects\PageSize;
use NextPDF\Contracts\Orientation;
$document = Document::createStandalone();
$document->setTitle('Quarterly Report');
// Intent is explicit: a typed page size and an Orientation enum case,
// not a string the engine has to interpret.
$document->addPage(PageSize::a4(), Orientation::Landscape);
$document->setFont('helvetica', 'B', 16);
// Ambiguous boolean is named, so the call reads as intent.
$document->cell(0, 12, 'Quarterly Report', newLine: true);
try {
// Unsafe path is rejected before a byte is built.
$document->save('php://output/report.pdf');
} catch (InvalidConfigException $e) {
// "Invalid configuration for key "output_path": expected valid_path, ..."
error_log($e->getMessage());
// The String destination is explicit: bytes only, no HTTP headers,
// no file side effect. Nothing is inferred from a missing filename.
$bytes = $document->output(dest: OutputDestination::String);
}

Er is geen pad waarlangs dit programma stilletjes iets verkeerds doet. Het drukt de intentie uit en gaat verder, of het benoemt het probleem en stopt.

Het veelgehoorde bezwaar is “dit is gewoon breedsprakigheid”. Het is geen breedsprakigheid. Het is de afwezigheid van verborgen standaardwaarden. Een kale true is korter dan newLine: true, maar neemt precies die duidelijkheid weg. De engine ruilt een paar tekens op de aanroepplek in voor het uitbannen van een categorie bugs — die waarbij de code compileert, draait, een bestand produceert en verkeerd is.

Een verwant misverstand is dat fail-fast “veel uitzonderingen werpt” betekent. Bij normaal gebruik werpt NextPDF niets. Geldige invoer stroomt erdoorheen. De guards vuren alleen op invoer die werkelijk dubbelzinnig of onveilig is — precies de invoer waarover u onmiddellijk wilt horen, niet de invoer waarvoor u wilt dat de engine gokt.

Weigeren te gokken geldt voor intentie en veiligheid, niet voor elk gemak. NextPDF heeft nog steeds veilige standaardwaarden: staande oriëntatie, links uitgelijnd, geen rand. Het principe is dat een standaardwaarde alleen wordt aangeboden waar die veilig is en niet verrast, en nooit waar de verkeerde gevolgtrekking een verkeerd document oplevert.

Deze pagina demonstreert het principe op het kernoppervlak van de publieke API (de documentfaçade, het contract ervan en het uitvoerpad). Subsystemen hebben hun eigen ingangen, en elk documenteert zijn eigen validatiegedrag. De hier geciteerde vormen zijn actueel op het moment van deze review. Ze illustreren het patroon; ze zijn geen uitputtende catalogus van elke guard in de engine.

De beschreven fail-fast-guards zijn correctheids- en veiligheidsguards. Op zichzelf vormen ze geen beveiligingsgrens. Invoervalidatie is één laag. De ontwerpfilosofie en de beveiligingsdocumentatie beschrijven de bredere houding.

  • Code-backed (bewijsniveau) — een pagina waarvan de beweringen worden getoetst aan de eigen broncode van de engine of een uitvoerbaar voorbeeld, geciteerd in plaats van geparafraseerd.
  • Fail fast — het weigeren van ongeldige invoer op het vroegst mogelijke punt, met een duidelijke oorzaak, in plaats van door te gaan en later op onduidelijke wijze te falen.
  • Benoemd argument — een PHP-aanroepsyntax (newLine: true) die een waarde op naam aan een parameter bindt, waardoor een anders dubbelzinnige literal zelfbeschrijvend wordt.
  • Levenscyclus voor eenmalig gebruik — het wegwerpbare Document-contract: instantiëren, schrijven, opslaan, weggooien. Geen reset(), geen hergebruik. Workers maken per verzoek een verse instantie via DocumentFactory.
  • PAdES — PDF Advanced Electronic Signatures, de familie van ETSI-profielen voor PDF-ondertekening. Bij het eerste gebruik uitgeschreven; uitgebreid behandeld op de ondertekeningspagina’s.