Rund vier Monate lang zeigte eines unserer Admin-Dashboards „Seite 1 von 0“ an, sobald eine Suche leer zurückkam. Niemand hat ein Ticket aufgemacht. Schuld war die Zeile max(1, min($lastPage, $requestedPage)), die dir bei $lastPage gleich 0 einfach 1 zurückgibt und dann weitermacht, als wäre nichts gewesen. Technisch kaputt war nie etwas. Es war nur ein bisschen falsch, und zwar leise falsch, deshalb ist es uns nie aufgefallen. PHP 8.6.0 RC3 wurde am 8. Oktober getaggt, die allgemeine Verfügbarkeit ist für den 19. November geplant. Nachdem ich mir die Änderungen durchgelesen habe, steht für mich fest: Das Wertvollste an diesem Release ist kein Syntax-Feature, sondern ein Temperament. Die Sprache wird lauter, wenn es um schlechte Eingaben geht, und jede neue Exception, auf die du beim Upgrade stößt, ist ein Bugreport, den du nicht selbst schreiben musstest.
Nimm die neue Funktion clamp(). Für mein Dashboard wäre sie ein hübscher Einzeiler, aber sie spielt nicht mit, wenn $min größer als $max ist oder eine der Grenzen NAN ist: Dann wirft sie einen ValueError. In meinem Fall ist das genau die Situation mit dem leeren Ergebnis, der Bug, mit dem ich monatelang gelebt habe, wäre also gleich beim ersten Mal aufgeflogen, als jemand nach Kauderwelsch gesucht hat. Dieselbe Stimmung zieht sich durch die ganze Standardbibliothek. Viele eingebaute Funktionen, die bei einem unsinnigen Flag oder einer Zahl außerhalb des gültigen Bereichs früher nur mit den Schultern gezuckt haben, werfen jetzt einen ValueError. preg_grep() gibt false zurück, wenn die Regex-Engine während der Ausführung scheitert, statt wie bisher so zu tun, als wäre nichts passiert. base_convert() und seine Geschwister melden eine Notice, wenn sie dir kein exaktes Ergebnis liefern können. Und ein return innerhalb eines finally-Blocks ist jetzt deprecated, weil es schon immer stillschweigend jede Exception verschluckt hat, die gerade auf dem Weg nach draußen war.
Selbst die glänzende neue Syntax hält sich an die Regel. Partial Function Application wurde einstimmig angenommen, und ein Folge-RFC hat sie vor dem Release noch verschärft: Jeder ?-Platzhalter erzeugt einen Pflichtparameter, auch wenn der Parameter darunter einen Default hat. Schreibst du round(?, ?), bekommst du eine Closure, die auf Zahl und Präzision besteht, obwohl round() selbst ohne das zweite Argument auskommt. Wenn die Defaults erhalten bleiben sollen, nimm ..., damit bleiben optionale Parameter optional. Diese Entscheidung gefällt mir sehr. Ein Fragezeichen in einem Aufruf sollte heißen „hier gebe ich dir einen Wert“ und nicht manchmal „oder vielleicht auch nicht“.
Jetzt zum stärksten Gegenargument, und das nehme ich ernst. Aus Sicht deiner Nutzer ist eine Seite mit einer leicht seltsamen Zahl besser als eine Seite mit einer Fehlermeldung. Viel PHP in Produktion ist über Jahre, meist ganz unabsichtlich, auf das alte nachsichtige Verhalten hin abgestimmt worden. Teams mit dünner Testabdeckung erfahren von diesen neuen Exceptions nicht im CI. Sie erfahren davon durch Kunden, mitten im Deploy, an einem Freitag. Versteckte Fehler in sichtbare Ausfälle zu verwandeln, ist im Prinzip richtig. In der Praxis verschiebt es die Kosten auf den, der gerade Bereitschaft hat.
Ich lande trotzdem auf der lauten Seite, auch weil Core dabei so sorgfältig vorgegangen ist. Stream-Fehler können jetzt als StreamException geworfen werden, das stellst du über eine error_mode-Option im Stream-Kontext ein, aber der Default bleibt bei Warnings. Bei niemandem wirft file_get_contents() plötzlich, wenn er es nicht ausdrücklich will. Die Deprecations erzeugen nur E_DEPRECATED, dein Code läuft also weiter. Und wo eine Deprecation nach mehr Aufwand als Nutzen aussah, hat die Abstimmung Nein gesagt: list() und der gettext-Alias _() bleiben beide. So sieht kein Team aus, das aus Prinzipienreiterei Dinge kaputt macht. Und der Kostenunterschied ist real. Eine Exception, die im Staging hochgeht, kostet dich einen Kaffee und einen kleinen Patch. Ein stiller falscher Wert kann dich ein ganzes Jahr fehlerhafter Datensätze kosten, die du danach von Hand abgleichen musst. RC4 und RC5 stehen noch im Kalender, du hast also Zeit, einen CI-Job auf den Release Candidate anzusetzen und ihn meckern zu lassen.
Und jetzt die Wendung, die mich stört. Das eine neue Feature, das still versagt, ist ausgerechnet Partial Function Application, also genau das Feature, auf das sich alle freuen. Gebundene Argumente werden ein einziges Mal ausgewertet, nämlich in dem Moment, in dem das Partial entsteht, und die Closure speichert die Ergebnisse. Stell dir einen langlaufenden Queue-Worker vor, der beim Booten ein Logging-Partial baut und date('c') als eines der Argumente einsetzt. Jeder Job, den er sechs Stunden später verarbeitet, trägt immer noch den Zeitstempel vom Boot. Du bekommst keinen Error, keine Notice, keine Deprecation. Eine Arrow Function hätte date() bei jedem Aufruf neu ausgeführt. Go-Entwickler haben jahrelang mit einer ähnlichen Capture-Falle bei ihren Schleifenvariablen gelebt, bis die Sprache das geändert hat, wir sind also in guter Gesellschaft. Aber in einem Release, das sonst so gern den Mund aufmacht, ist das genau die Stelle, an der dir niemand etwas sagt. Ich würde für jedes Partial, dessen gebundene Argumente einen Funktionsaufruf enthalten, eine Review-Regel oder einen Check in der statischen Analyse einführen.
Deshalb meine Frage an dich: Wo sollte die Grenze verlaufen? Sollte eine künftige Version oder dein Tooling warnen, wenn ein Partial etwas bindet, das weder eine Konstante noch eine einfache Variable ist? Oder ist das genau die Art Händchenhalten, die du am zweiten Tag abschaltest? Und wenn du Code hast, in dem ein stiller Fallback tatsächlich etwas zusammenhält, wo ein geworfener ValueError mehr wehtun würde als die falsche Antwort, die er ersetzt, dann erzähl mir in den Kommentaren davon. Das ist das stärkste Argument gegen meine These, und das würde ich gern sehen.




Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.