Eine API, die nicht rät
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“NextPDF verlangt, dass Sie sagen, was Sie meinen. Wo die Absicht die Bytes verändert — eine Signaturstufe, ein Ausgabeziel, ein Konformitätsziel —, ist sie ein erforderliches, ausdrückliches Argument und nichts, was die Engine aus dem Kontext ableitet.
Diese Seite zeigt diese Haltung im eigenen Quelltext der Engine: in den Methodensignaturen, den benannten Argumenten und den Stellen, an denen eine mehrdeutige Eingabe abgewiesen wird, bevor auch nur ein Byte erzeugt wird.
Warum das wichtig ist
Abschnitt betitelt „Warum das wichtig ist“Eine Vermutung ist eine Entscheidung, die in Ihrem Namen getroffen wird, ohne dass sie Ihnen offengelegt wird. Bei einem Textfeld ist das mäßig ärgerlich. Bei einem PDF ist es ein latenter Mangel, weil das, was Sie ausliefern, oft ein rechtliches oder archivarisches Artefakt ist, dessen Korrektheit später von jemand anderem mit einem Validator geprüft wird.
Betrachten Sie eine Signatur. Ihr Digest wird über einen deklarierten Bytebereich berechnet, der den Signaturwert selbst bewusst ausschließt ( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). Eine API, die still “hilft” — Struktur umschreibt, eine Stufe ableitet, einen Platzhalter auffüllt —, hat nicht geholfen. Sie hat die Bytes verändert, die eine Signatur schützen sollte. Die Vermutung, die an der Aufrufstelle freundlich erscheint, wird Wochen später zum Produktionsvorfall. Es ist dieselbe Codezeile.
Die Kurzfassung
Abschnitt betitelt „Die Kurzfassung“- Wenn eine Wahl die Ausgabe verändert und keinen sicheren Standard hat, macht NextPDF sie zu einem erforderlichen Argument, statt sie abzuleiten.
- Optionale Argumente, die sich mehrdeutig deuten lassen, werden benannt, sodass die Aufrufstelle die Absicht angibt (
newLine: true, nicht ein blankestrue). - Eingaben, die unsicher sein könnten, werden vor dem Rendering validiert und mit einer typisierten Ausnahme abgewiesen, die die Ursache benennt.
- Eine Dokumentinstanz ist einmalig verwendbar: Sie wird erstellt, ausgegeben und verworfen. Es gibt kein
reset(), also auch kein Raten darüber, ob dieses Ding wiederverwendet wird. - Die Engine gibt niemals ein plausibel aussehendes Artefakt anstelle des angeforderten aus. Stattdessen verweigert sie die Ausgabe.
Wie NextPDF damit umgeht
Abschnitt betitelt „Wie NextPDF damit umgeht“Der Mechanismus ist unspektakulär, und genau das ist der Punkt. Das Typsystem, benannte Argumente, Enums statt magischer Strings und eine kleine Anzahl bewusster Guard-Klauseln stehen vor der Ausgabe.
Die Tabelle zeigt einige mehrdeutige Eingaben. Für jede zeigt sie, was eine Bibliothek, die “hilft”, ableiten würde, und was NextPDF stattdessen tut. Jeder Eintrag in der NextPDF-Spalte ist ein Verhalten, das aus dem weiter unten auf dieser Seite gezeigten Quelltext zitiert ist.
| Mehrdeutige Eingabe | Was eine ratende Bibliothek tut | Was NextPDF tut |
|---|---|---|
Ein Ausrichtungs-String wie "portait" | Fällt auf einen Standard zurück und rendert trotzdem | addPage() nimmt das Enum Orientation, keinen String — ein Tippfehler ist ein Typfehler, kein stiller Standard |
Ein blankes nachgestelltes true an cell() | Wählt die boolesche Position, von der sie annimmt, dass Sie sie gemeint haben | Der Boolean wird an der Aufrufstelle benannt (newLine: true); ein unbenanntes Literal ist genau das Muster, das die API beseitigt |
Ein php://-Wrapper oder ein Traversal-Pfad an save() | ”Gibt sein Bestes” und schreibt irgendwohin | Vor dem Bau des PDF abgewiesen, mit einer typisierten InvalidConfigException, die Schlüssel, Wert und erwarteten Typ benennt |
setSignature() dann save(), während der High-Level-Signierer nicht verdrahtet ist | Gibt eine unsignierte Datei aus, die der Aufrufer für signiert hält | Wirft NotImplementedException, bevor Bytes erzeugt werden, und benennt den unterstützten Weg |
Wiederverwendung einer Document-Instanz für ein zweites Rendering | Rät, ob verbleibender Zustand noch gilt | Kein reset() und kein Wiederverwendungspfad — eine frische Instanz pro Anfrage über DocumentFactory, sodass es keinen verbleibenden Zustand gibt, über den geraten werden müsste |
Die Absicht ist ein erforderliches Argument. Der Kernvertrag, PdfDocumentInterface, nimmt Geometrie und Ausrichtung als typisierte Value Objects und Enums, nicht als lose Primitive:
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 und Alignment sind Enums. Ein Aufruf kann also nicht "portait" übergeben, das dann stillschweigend “Standard” bedeutet. Wo ein Standard existiert, ist er sicher (Hochformat, links, kein Rahmen) und keine Vermutung darüber, was Sie vermutlich wollten.
Mehrdeutige Booleans werden an der Aufrufstelle benannt. In den Beispielen, die als De-facto-API-Referenz dienen, wiederholt sich dieselbe Form:
$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 ist unmissverständlich. Ein blankes nachgestelltes true wäre es nicht. Die Signaturstufe ist SignatureLevel::PAdES_B_B, ein Enum-Fall — niemals ein String, den die Engine interpretieren müsste. Das Ausgabeziel ist OutputDestination::String; damit ist “gib mir die Bytes, keine HTTP-Header, keine Datei” ausdrücklich angegeben und wird nicht daraus abgeleitet, ob ein Dateiname übergeben wurde.
Unsichere Eingabe wird abgewiesen, bevor ein Byte geschrieben wird. save() validiert den Zielpfad, bevor es das PDF baut:
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}Die Engine versucht bei einem php://-Wrapper oder einem Traversal-Pfad nicht, “ihr Bestes” zu geben. Sie verweigert die Eingabe, und die Ausnahme benennt den Schlüssel, den Wert und das, was erwartet wurde.
Die Engine verweigert, statt ein irreführendes Artefakt auszugeben. Die stärkste Form, nicht zu raten, besteht darin, überhaupt keine Ausgabe zu erzeugen, wenn diese Ausgabe unwahr wäre. Ist eine High-Level-Signatur konfiguriert, der Writer-Übergang, der tatsächlich signieren würde, aber nicht verdrahtet, wirft der Build-Pfad vor dem Erzeugen von Bytes, statt eine unsignierte Datei auszugeben, die der Aufrufer für signiert hält:
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 ...', );}Ein unsigniertes PDF, das signiert aussieht, ist genau die Art plausibel aussehenden, falschen Artefakts, deren Verhinderung Zweck dieses Prinzips ist. Dieselbe Haltung zeigt sich im strikten CSS-Pfad. Eine nicht registrierte Spec-Abweichung wirft zum Zeitpunkt der Erkennung eine StrictModeViolation, statt eine Annäherung zu rendern und die Abweichung unentdeckt zu lassen.
Einmalige Verwendung beseitigt eine ganze Klasse von Vermutungen. Ein Document ist wegwerfbar — erstellt, ausgegeben, verworfen. Es gibt kein reset() und keinen Wiederverwendungspfad. Ein langlaufender Worker erstellt pro Anfrage eine frische Instanz über DocumentFactory. Die Engine muss nie raten, ob verbleibender Zustand aus einem vorherigen Dokument noch bedeutsam ist, weil es per Konstruktion keinen gibt.
Was die Belege sagen
Abschnitt betitelt „Was die Belege sagen“Diese Seite ist Evidence: Code-backed : Jede oben gezeigte Form stammt aus dem eigenen Quelltext der Engine und ihren Beispielen; sie ist zitiert, nicht aus der Absicht umschrieben.
- Die typisierten Signaturen mit Enums sind der öffentliche Vertrag in
PdfDocumentInterface. Der Aufrufstil mit benannten Argumenten ist die durchgängige Form in den kanonischen Beispielen, die als De-facto-API-Referenz dienen. - Die Pfad-Validierung vor dem Rendering mit ihrer typisierten
InvalidConfigExceptionund der Refuse-before-emit-GuardNotImplementedExceptionsind wörtlich aus dem Ausgabepfad der Dokument-Fassade zitiert. - Der Standards-Anker ist Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 — Schutz vor Benutzerfehlern, die Qualitätseigenschaft, die eine API, die nicht rät, an der Aufrufstelle erfüllen soll. Der zweite Anker ist Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 , weshalb das Raten rund um ein signiertes Dokument nie harmlos ist. Der Digest deckt einen deklarierten Bytebereich ab, der den Signaturwert ausschließt, sodass jede stille Umschreibung ihn ungültig macht.
Praktisches Beispiel
Abschnitt betitelt „Praktisches Beispiel“Ein kleines, vollständiges Programm. Jede Zeile, die mehrdeutig sein könnte, macht ihre Absicht ausdrücklich. Die einzige unsichere Eingabe wird abgewiesen, bevor irgendeine Arbeit getan wird.
<?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);}Es gibt keinen Pfad, auf dem dieses Programm still das Falsche tut. Entweder gibt der Aufruf die Absicht an und läuft weiter, oder er benennt das Problem und hält an.
Häufiges Missverständnis
Abschnitt betitelt „Häufiges Missverständnis“Der häufige Einwand lautet: “Das ist bloß Wortreichtum.” Es ist kein Wortreichtum. Es ist die Abwesenheit versteckter Standards. Ein blankes true ist kürzer als newLine: true — und zwar genau um das Maß an Klarheit, das es entfernt. Die Engine tauscht einige Zeichen an der Aufrufstelle gegen die Beseitigung einer Kategorie von Fehlern — derjenigen, bei der der Code kompiliert, läuft, eine Datei erzeugt und falsch ist.
Ein verwandtes Missverständnis ist, dass Fail Fast “wirft viel” bedeutet. Im normalen Gebrauch wirft NextPDF nichts. Gültige Eingabe fließt durch. Die Guards greifen nur bei Eingaben, die wirklich mehrdeutig oder unsicher sind — genau die Eingaben, über die Sie sofort informiert werden möchten, statt sie erraten zu lassen.
Grenzen und Abgrenzungen
Abschnitt betitelt „Grenzen und Abgrenzungen“Nicht zu raten gilt für Absicht und Sicherheit, nicht für jede Bequemlichkeit. NextPDF hat weiterhin sichere Standards: Hochformat-Ausrichtung, linke Ausrichtung, kein Rahmen. Das Prinzip ist, dass ein Standard nur dort angeboten wird, wo er sicher und unüberraschend ist, und niemals dort, wo die falsche Ableitung ein fehlerhaftes Dokument erzeugt.
Diese Seite demonstriert das Prinzip an der zentralen öffentlichen API-Oberfläche (der Dokument-Fassade, ihrem Vertrag und dem Ausgabepfad). Subsysteme haben ihre eigenen Einstiegspunkte, und jedes dokumentiert sein eigenes Validierungsverhalten. Die hier zitierten Formen sind zum Zeitpunkt dieser Prüfung aktuell. Sie veranschaulichen das Muster; sie sind kein vollständiger Katalog aller Guards in der Engine.
Die beschriebenen Fail-Fast-Guards sind Korrektheits- und Sicherheits-Guards. Sie sind für sich genommen keine Sicherheitsgrenze. Eingabevalidierung ist eine Schicht. Die Design-Philosophie und die Sicherheitsdokumentation beschreiben die umfassendere Haltung.
Verwandte Dokumentation
Abschnitt betitelt „Verwandte Dokumentation“- Die Design-Philosophie von NextPDF — das Prinzip, das diese Seite demonstriert, im Kontext seiner Prioritäten.
- Fehler als Feature — was die typisierten Ausnahmen, die diese Guards werfen, Ihnen mitteilen sollen.
- Strikte Typen, überall — wie das Typsystem “Geben Sie Ihre Absicht an” durchsetzbar macht, statt es nur zu empfehlen.
Glossar
Abschnitt betitelt „Glossar“- Code-gestützt (Belegstufe) — eine Seite, deren Aussagen am eigenen Quelltext der Engine oder an einem lauffähigen Beispiel geprüft werden; zitiert statt umschrieben.
- Fail Fast — eine ungültige Eingabe am frühestmöglichen Punkt mit klarer Ursache abweisen, statt fortzufahren und später undurchsichtig zu scheitern.
- Benanntes Argument — eine PHP-Syntax an der Aufrufstelle (
newLine: true), die einen Wert per Name an einen Parameter bindet und ein sonst mehrdeutiges Literal selbsterklärend macht. - Einmalig verwendbarer Lebenszyklus — der wegwerfbare
Document-Vertrag: instanziieren, schreiben, speichern, verwerfen. Keinreset(), keine Wiederverwendung. Worker erstellen pro Anfrage eine frische Instanz überDocumentFactory. - PAdES — PDF Advanced Electronic Signatures, die ETSI-Profilfamilie für PDF-Signierung. Bei erster Verwendung ausgeschrieben; auf den Signierseiten ausführlich behandelt.