Vor einer Weile habe ich einen Nachmittag an eine Retry-Einstellung mit dem Wert 300 verloren. Der Entwickler, der sie geschrieben hat, meinte Millisekunden. Der Worker, der sie las, ging von Sekunden aus, also parkte sich jeder fehlgeschlagene Job für fünf Minuten und die Queue lief voll, bis mich jemand anpiepte. Nichts im Typsystem hätte das abfangen können, denn beide Seiten reichten einfach nur einen int herum. PHP 8.6, angekündigt für den 19. November 2026, geht genau diese Klasse von Bugs endlich frontal an, und das, mehr als jede neue Syntax, ist der Grund, warum ich mich auf dieses Release freue.

backoff.php
<?php
use Time\Duration;

// backoff math stays in duration space, no unit juggling
$base = Duration::fromMilliseconds(100);
$attempt = 5;
$delay = $base->multiplyBy(2 ** $attempt);

$timeout = Duration::fromSeconds(2)
    ->add(Duration::fromMilliseconds(500));

if ($delay < $timeout) {
    usleep($delay->seconds * 1000000);
}

Das Feature, das auf jedem Meetup vorgeführt werden wird, ist Partial Function Application. Du schreibst number_format(?, 2, '.', ' ') und bekommst eine Closure zurück, die auf das fehlende Argument wartet, und mit ... kannst du ganze Enden einer Signatur offen lassen. Zusammen mit dem Pipe-Operator heißt das: Eine Transformationskette braucht nicht mehr an jedem Schritt eine Wegwerf-Arrow-Function. Dazu kommen readonly-Properties mit Default-Werten, #[\Override] jetzt auch für Klassenkonstanten und Enums, die __debugInfo() implementieren können, insgesamt eine solide Wundertüte. Alles gut und schön. Aber meine Stimme für die Schlagzeile geht an Time\Duration.

Duration ist ein richtiger Wertetyp für Zeitspannen. Du baust eine mit fromSeconds() oder fromMilliseconds(), und intern hält sie drei Teile: Sekunden, Nanosekunden und ein Negativ-Flag. Aus 1500 Millisekunden wird also eine Sekunde plus 500 Millionen Nanosekunden, ohne dass du die Arithmetik selbst machst. Instanzen sind unveränderlich, add() und multiplyBy() geben frische Objekte zurück, und zwei Durations lassen sich direkt mit < und Konsorten vergleichen. Exponentielles Backoff wird zur Multiplikation auf einem Wert, der weiß, was er ist. Wir haben vor Jahren aufgehört, Geld als nackte Floats zu speichern, weil die Einheit unterwegs ständig verloren ging. Ein Timeout als blanker Integer ist dieselbe Falle, und 8.6 schließt sie endlich.

Um dieses Herzstück herum sitzen kleinere Standardisierer, die ich Quality-of-Life-Gesetzgebung nennen würde. clamp($volume, 0, 100) ersetzt die max()-um-min()-Verrenkung, die jede Codebasis neu erfunden hat, und akzeptiert alles Vergleichbare, DateTimeImmutable eingeschlossen. SortDirection kommt als Core-Enum mit den Cases Ascending und Descending, das ewige 'asc' gegen 'ASC' gegen true in Repository-Methoden bekommt also endlich einen echten Typ. Und JsonException-Meldungen tragen jetzt Koordinaten und melden einen Syntaxfehler nahe Position 1:3, statt dich irgendwo aus einem 40 KB großen Payload heraus achselzuckend anzuschauen. Jedes dieser Dinge ist winzig. Jedes davon schickt aber auch eine Helper-Funktion in Rente, die in fünf inkompatiblen Geschmacksrichtungen quer durch dein vendor-Verzeichnis existierte.

Und dann ist da das Stück, das sich Server-Side-Leute genau ansehen sollten: die neue Polling API. Mit Io\Poll\Context registriert ein Prozess Stream-Handles mit Events wie Event::Read und kann dann per wait() auf alle gleichzeitig warten, während PHP darunter den besten OS-Mechanismus auswählt: epoll auf Linux, kqueue auf macOS und BSD, WSAPoll auf Windows, Event Ports oder schlichtes poll anderswo. Bis jetzt war die eingebaute Antwort stream_select(), das die Skalierungsdecken des uralten select()-Syscalls erbt. Um beim Umfang ehrlich zu sein: Das ist ein Warte-Primitiv, kein Scheduler, keine Event Loop, kein async-Keyword. Aber Projekte wie Amp und ReactPHP, die sich heute entweder auf stream_select() stützen oder dein Ops-Team bitten müssen, eine Extra-Extension zu installieren, bekommen ein natives Fundament in jedem Standard-Build von PHP. Und ja, wait() nimmt eine Duration als Timeout, Null für einen nicht blockierenden Check, null zum Blockieren, bis etwas passiert. Die neuen Teile greifen schon ineinander.

Jetzt zum ehrlichen Gegenargument. Syntax ist das, was deine Diffs vom ersten Tag an verändert, und Partial Application wird den Alltagscode weit sichtbarer umkrempeln als jedes Duration-Objekt. Das gebe ich zu, und ich lege die Fußnoten dazu, die man kennen sollte: Argumente, die du beim Bauen eines Partials mitgibst, werden genau dort ausgewertet, zur Erstellungszeit. Ein Config-Lookup, der in ein Partial eingebacken ist, friert also ein, während derselbe Lookup in einer Arrow Function bei jedem Aufruf neu läuft. Und ein Partial ist eine brandneue anonyme Closure; frag die Reflection, wer sie ist, und sie verrät dir nichts über die umwickelte Funktion, während strlen(...) seine Identität behält, Attribute inklusive. Warum stufe ich die Bibliotheksarbeit trotzdem höher ein? Weil du Currying mit fn() und etwas Disziplin schon immer faken konntest. Kein Composer-Paket kann Duration zu dem Einheitentyp machen, auf den sich PDO-Treiber, HTTP-Clients und dein eigener Code alle einigen, und keines kann epoll in die Standard-Runtime bringen. Nur der Core kann gemeinsames Vokabular prägen.

Bevor du upgradest, plane Zeit fürs Aufräumen ein. Die Session-Defaults werden ab Werk strenger: session.use_strict_mode, cookie_httponly und ein SameSite-Wert von Lax sind jetzt die Grundlinie, was die richtige Entscheidung ist, aber Cross-Site-Login-Flows und jedes JavaScript, das den Session-Cookie liest, überraschen wird. Ein return aus einem finally-Block ist deprecated, ebenso Rückgabewerte aus __construct() und __destruct(). spl_object_hash() ist auf dem Weg nach draußen; spl_object_id() ersetzt es, gibt aber einen int zurück statt eines 32 Zeichen langen Strings, also grep nach Code, der diese Hashes speichert oder vergleicht. Legacy-Aliase wie is_double() sowie strcoll(), metaphone() und die Objekt-zu-Array-Umwandlung stehen mit auf der Deprecation-Liste, die Bezeichner let, is und readonly werden für Klassen- und Funktionsnamen reserviert, und die trim()-Familie entfernt jetzt standardmäßig auch Form-Feed-Zeichen. PHPCompatibility, Rector und eine PHPStan-Baseline machen aus all dem eine Checkliste statt einer Krise.

Und hier will ich wirklich Widerspruch hören. Jede dieser Neuerungen überlappt mit etwas, das Userland längst gebaut hat: Zeitobjekte im Stil von Carbon, Sort-Enums in Frameworks, handgestrickte clamp-Helper, ext-uv. Das eine Lager sagt, dass der Core mit der Übernahme dieses Terrains endlich eine Konvention statt sechs liefert. Das andere sagt, er fügt nur eine siebte hinzu, und der Interop-Schmerz zwischen Time\Duration und DateInterval oder zwischen SortDirection und dem, was dein ORM 2019 erfunden hat, wird den Nutzen überdauern. Wo landest du, und was sollte der Core als Nächstes standardisieren: einen Money-Typ, ein echtes URI-Objekt, oder erst mal eine Weile gar nichts?