Strenge Typisierung überall
Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 Evidence: Code-backed PHPStan: Level 10, no src baseline
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“NextPDF lässt PHPStan auf Level 10 über den Engine-Quellcode laufen, ohne Unterdrückungs-Baseline. Diese Seite erklärt, warum „keine Baseline“ eine Designentscheidung und kein Werkzeugdetail ist und welchen Nutzen diese Strenge für eine Pipeline tatsächlich hat, deren einzige Aufgabe darin besteht, Daten nicht falsch zu verarbeiten.
Warum das wichtig ist
Abschnitt betitelt „Warum das wichtig ist“In den meisten Anwendungen ist strenge Typisierung eine Frage der Hygiene. In einer PDF-Engine ähnelt sie eher einem Korrektheitsmechanismus. Das Format verzeiht keine Fehler. Von einem Reader wird erwartet, dass er Inhalte findet, indem er die Datei von ihrem Ende her über den Trailer und die Querverweistabelle liest. Deshalb müssen die Byte-Offsets eines Writers exakt sein. Man denke an einen Typ, der sich stillschweigend zu mixed erweitert, an einen int, der unbemerkt zu einem string wird, oder an einen Nullable-Wert, der ungeprüft dereferenziert wird. Jeder dieser Fälle kann eine Datei erzeugen, die sich in einem Viewer einwandfrei öffnet und in einem anderen die Validierung nicht besteht – Wochen später, ohne Stack Trace, der auf die Ursache zurückweist.
Die kostspieligen Fehler in diesem Bereich sind die stillen. Strenge Typisierung in Verbindung mit einem strengen Analyzer ist das Mittel, mit dem die Engine eine ganze Klasse stiller Laufzeitfehler in laute Fehler zur Build-Zeit verwandelt.
Die Kurzfassung
Abschnitt betitelt „Die Kurzfassung“- Der Engine-Quellcode wird auf PHPStan Level 10 analysiert – der strengsten Stufe –, wie in
phpstan.neon.distverifiziert. - Es gibt keine Unterdrückungs-Baseline für den Quellcode. Die Konfiguration legt die Quellcode-Analyse auf null Fehler fest. Eine Regression lässt den Build fehlschlagen, statt in einer wachsenden Ignore-Datei aufzugehen.
- Die wenigen vorhandenen
ignoreErrors-Einträge sind in der Konfiguration eng gefasst, auf Identifier und Pfad beschränkt und einzeln begründet (paketübergreifende Soft-Dependency-Grenzen und Reflection-Target-Testnahtstellen) – keine pauschale Baseline. - Ein separates strenges Profil läuft mit
level: maxund untersagt jegliche neuen Ignore-Einträge, sodass neuer Code an einen noch strengeren Maßstab gebunden ist. - Die beabsichtigte Wirkung ist Designdruck: Code, der sich nicht typehrlich ausdrücken lässt, kommt nicht durch und wird daher neu entworfen statt unterdrückt.
Wie NextPDF dabei vorgeht
Abschnitt betitelt „Wie NextPDF dabei vorgeht“Der Unterschied zwischen „wir verwenden einen strengen Analyzer“ und „wir verwenden einen strengen Analyzer ohne Baseline“ ist entscheidend; deshalb lohnt sich Präzision.
Eine Baseline erfasst jede bestehende Verletzung und weist den Analyzer an, genau diese zu ignorieren. Das ist eine pragmatische Methode, um statische Analyse in einer Altcodebasis einzuführen, hat aber ihren Preis. Die Baseline wird zu einem stillen Schuldenregister, bei dem sich das Typsystem stillschweigend darauf einlässt, wegzusehen. Neue Verletzungen derselben Art können sich neben den im Bestand geduldeten einschleichen. Das Versprechen des Analyzers schwächt sich von „dieser Code ist typsauber“ zu „dieser Code ist nicht schlechter als zuvor“ ab.
NextPDF geht diesen Kompromiss für den Engine-Quellcode nicht ein. Die Konfiguration legt die Quellcode-Analyse auf null Fehler fest und aktiviert reportUnmatchedIgnoredErrors, sodass selbst eine veraltete Unterdrückung – eine, die auf nichts mehr zutrifft – den Build fehlschlagen lässt. Die wenigen verbleibenden, eng gefassten Ignores sind auf einen bestimmten Fehler-Identifier und eine bestimmte Datei beschränkt. Jeder enthält eine Inline-Erläuterung, warum die Grenze beabsichtigt ist (Core ist zum Beispiel gegen eine Pro-/Enterprise-Schnittstelle programmiert, von der es bewusst nicht konkret abhängt). Ein Reviewer kann jeden einzelnen lesen und beurteilen. Es gibt keine undurchsichtige Liste, über die man den Überblick verlieren könnte.
Dieser Ablauf hält das System ehrlich:
- Change proposed New or modified engine code.
- Level 10 analysis Strictest PHPStan level over src/, treatPhpDocTypesAsCertain on.
- Zero-error gate No source baseline; unmatched ignores also fail.
- Strict profile level: max; no new ignore entries permitted.
- Redesign, not suppress If it cannot be expressed honestly, the design changes.
treatPhpDocTypesAsCertain gehört dazu. PHPDoc-Annotationen werden als verbindliche Wahrheit behandelt, sodass ein @param list<T> oder ein @return non-empty-string kein Kommentar ist, den der Analyzer höflich ignoriert. Es ist ein geprüftes Versprechen. Annotation und Laufzeittyp müssen übereinstimmen.
Was die Belege sagen
Abschnitt betitelt „Was die Belege sagen“Diese Seite ist Evidence: Code-backed . Die Konfiguration ist der Beleg:
phpstan.neon.distsetztlevel: 10,phpVersion: 80400, analysiertsrcund enthält keinenbaseline:-Schlüssel – es gibt keinephpstan-baseline.neonfür die Quellcode-Analyse.- Dieselbe Datei setzt
treatPhpDocTypesAsCertain: trueundreportUnmatchedIgnoredErrors: true, mit einer Inline-Notiz, dass die L10-Quellcode-Analyse auf null Fehler festgelegt ist und jede Regression die CI fehlschlagen lassen muss. - Die verbleibenden
ignoreErrorssind jeweils auf einenidentifierund oft einenpathbeschränkt, mit Kommentaren, die die Soft-Dependency- und Reflection-Target-Begründung erläutern – sie sind keine pauschal generierte Baseline. phpstan-strict.neon.disterbt diese Konfiguration, hebt die Stufe aufmaxan und friert die Ignore-Liste ein, sodass unter dem strengen Profil kein neuer Eintrag hinzugefügt werden darf.
Der Bezug zu den Standards ist unmittelbar. Die Engine muss Dateien erzeugen, in denen ein Reader vom Trailer und der Querverweistabelle aus navigieren kann, gemäß Spec: ISO 32000-2, §7.5.5 ISO 32000-2 §7.5.5 . Exakte Byte-Offsets sind ein Typisierungsproblem, bevor sie ein Serialisierungsproblem sind. Ein Offset ist eine Ganzzahl, die niemals stillschweigend zu etwas anderem werden darf. Eine Pipeline, die auf Level 10 typsauber ist, hat bereits die meisten Wege beseitigt, auf denen Arithmetik stillschweigend schiefgehen kann.
Praktisches Beispiel
Abschnitt betitelt „Praktisches Beispiel“Strenge Typisierung ist dort am sichtbarsten, wo eine Domänenregel als Typ statt als Laufzeitprüfung codiert wird. Der Konformitätsdiskriminator beantwortet Fragen auf Spezifikationsebene mit erschöpfendem match, sodass ein nicht behandelter Fall ein Typfehler ist und kein falsches PDF:
declare(strict_types=1);
enum ConformanceMode: string{ case Plain = 'plain'; case PdfUa2 = 'pdfua2'; case PdfA4 = 'pdfa4';
/** @return 2|3|4|null */ public function pdfaPart(): ?int { return match ($this) { self::PdfA4 => 4, default => null, }; }}Das @return 2|3|4|null ist keine bloße Dokumentation. Unter
treatPhpDocTypesAsCertain wird es geprüft. Ein Aufrufer, der annimmt, das Ergebnis sei immer ein int, wird zur Analysezeit darauf hingewiesen – bevor auch nur ein einziges Byte einer nicht konformen PDF/A-Teilnummer jemals geschrieben wird.
Häufiges Missverständnis
Abschnitt betitelt „Häufiges Missverständnis“Die Falle besteht darin, „keine Baseline“ als „der Code weist zufällig keine Verletzungen auf“ zu lesen. Das ist verkehrt herum gedacht. Das Fehlen einer Baseline ist die Ursache, kein glücklicher Zufall. Weil es keinen Ort gibt, an dem sich eine Verletzung parken ließe, muss Code, der eine solche erzeugen würde, anders geschrieben werden. Level 10 ohne Quellcode-Baseline ist eine Einschränkung, die das Design formt, und kein Zeugnis, das es im Nachhinein beschreibt.
Ein zweites Missverständnis lautet, die Handvoll ignoreErrors-Einträge sei eine Baseline unter anderem Namen. Das sind sie nicht. Eine Baseline wird pauschal generiert und ist undurchsichtig. Diese Einträge sind einzeln verfasst, auf einen Identifier beschränkt, erläutert und durch reportUnmatchedIgnoredErrors abgesichert, sodass sie nicht unbemerkt veralten können.
Grenzen und Abgrenzungen
Abschnitt betitelt „Grenzen und Abgrenzungen“Diese Seite behandelt die Analyse des Engine-Quellcodes. Die Testsuite wird in einem separaten, bewusst abgegrenzten Scope und mit einer eigenen Konfiguration analysiert; „keine Baseline“ bezieht sich hier auf src/ und ist keine Behauptung, dass jede Hilfsanalyse im Repository baseline-frei ist. PHPStan beweist Typkorrektheit, nicht Verhaltenskorrektheit. Es ersetzt nicht die Testpyramide, sondern beseitigt nur eine Kategorie von Fehlern, denen die Tests andernfalls hinterherjagen müssten. Die genaue Stufe, die Flags und die Ignore-Menge sind zum Prüfdatum dieser Seite zutreffend. Die maßgebliche Quelle sind stets phpstan.neon.dist und phpstan-strict.neon.dist im Core-Repository.
Die Edition ändert nichts an dieser Disziplin. Jede Edition wird aus demselben Level-10-Quellcode erstellt:
| Edition | Availability |
|---|---|
| Core | Der Core-Quellcode wird auf Level 10 ohne Quellcode-Baseline analysiert. |
| Pro | Pro baut auf derselben Level-10-Quellcode-Disziplin auf. |
| Enterprise | Enterprise baut auf derselben Level-10-Quellcode-Disziplin auf. |
Verwandte Dokumentation
Abschnitt betitelt „Verwandte Dokumentation“- Die PHP-8.4-Grundlagen – die Sprachfeatures, auf die sich das Typsystem stützt.
- Fehler als Feature – was mit den Fehlern geschieht, die strenge Typisierung zutage fördert.
- Das Pipeline-Modell – die Architektur, die diese Disziplin schützt.
Glossar
Abschnitt betitelt „Glossar“- PHPStan Level 10 – die strengste Analysestufe, die untypisierte und lose typisierte Werte als Fehler statt als Warnungen behandelt.
- Baseline – eine generierte Liste bestehender Verletzungen, die der Analyzer ignorieren soll. NextPDF verwendet für den Engine-Quellcode keine.
treatPhpDocTypesAsCertain– eine PHPStan-Einstellung, die PHPDoc-Typannotationen als geprüfte Fakten behandelt, nicht als unverbindliche Kommentare.reportUnmatchedIgnoredErrors– eine Einstellung, die den Build fehlschlagen lässt, wenn ein Ignore-Eintrag auf nichts mehr zutrifft, und so veraltete Unterdrückungen verhindert.- Designdruck – die Wirkung einer Einschränkung, die erzwingt, dass Code auf eine bestimmte Weise geschrieben wird, im Gegensatz zu einer Prüfung, die ihn nur misst.