Die Regeln

Jede dieser Regeln ersetzt eine Gewohnheit, die still scheitert, durch eine, die laut scheitert. Sie sind der Grund, warum der Code so aussieht, wie er aussieht.

Nichts baut HTML aus einem String

Eine View liefert einen Node. Nur Element und Doctype schreiben je ein <, und Escaping und die Prüfung des URL-Schemas stecken beide in render() — also gelten sie, wie auch immer ein Element gebaut wurde, und ein javascript:-Link wird abgelehnt, wo er geschrieben würde, statt gerendert zu werden.

Handgeschriebenem HTML wird nie vertraut, es wird geparst

Es gelangt nur über Element::containingHtml() in den Baum, das es gegen das Vokabular der App parst und jedes Tag und Attribut ablehnt, das die App nicht deklariert hat. Nichts, worauf eine Anfrage Einfluss hat, wird je geparst — und diese Seite wird gar nicht geparst: Sie ist ein Baum, den ihre View baut, in jeder ihrer Sprachen.

Sichtbarer Text ist übersetzbar, und eine View nennt nie eine Sprache

Ein Katalog ist ein Enum, jeder Case trägt seine Worte in jeder Sprache, die die App anbietet, und der Baum löst jeden beim Rendern gegen das nächstgelegene lang auf. Ein Satz mit einem Link oder einem Stück Code darin ist ein Sentence, dessen Teile jede Sprache dorthin setzt, wo ihre Wortstellung sie haben will. Übersetzbarer Text ohne ein lang darüber wirft eine Exception, statt zu raten.

Namen und Werte sind typisiert

Ein Header ist ein HeaderName-Case und ein HeaderValue. Ein Attribut ist ein AttributeName-Case. Eine Adresse ist ein Path-Case, und ein Link ist ->to(…), nie eine Verkettung. Ein Wert mit einer Grammatik ist eine Klasse, ein festes Vokabular ist ein Enum — also ist ein falsch geschriebener Name ein Fehler dort, wo er geschrieben wird, und kein Standardwert irgendwo anders.

Eine Gruppe ist eine Collection

Eine Gruppe, die eine öffentliche Grenze überquert, ist eine unveränderliche, lazy ausgewertete Collection oder SearchableCollection: with() kopiert, und eine Kette von Schritten verschmilzt beim ersten Aufruf, der ein Ergebnis braucht, zu einem einzigen Durchlauf.

Fünf Gewohnheiten werden abgelehnt

Die ersten drei dürfen entschuldigt werden, durch ein Attribut, das seinen Grund trägt: #[BareArray], #[BareString], #[BareCall].

Ein Ergebnis, das zählt, kann nicht verloren gehen

Builder und Abfragen tragen #[\NoDiscard] mit einer Nachricht, also löst ein kopierender Aufruf, dessen Kopie weggeworfen wird, eine Warnung aus — und die Testsuiten scheitern an Warnungen.

Nichts beendet eine Anfrage außer der App, und jede Entscheidung kehrt zurück

Das answer() einer Antwort ist ein Answer, und App::handle() beantwortet eine ganze Anfrage, ohne sie zu senden. Nur App::run() sendet. Die Ablehnung eines Gates ist ein Wert, den es zurückgibt, markiert mit #[\NoDiscard], sodass eine fallengelassene die Suite scheitern lässt, und der Aufrufer gibt sie seinerseits zurück. Nichts ruft exit auf. So ist jede Antwort, ein 401, ein 303 oder ein 405 eingeschlossen, ein Status, Header und ein Body, die ein Test im selben Prozess lesen kann, über Phpanta\Test\TestRequest.

Die vollständige Begründung für jede steht in docs/guidelines.md und CLAUDE.md.