Öffne die Datei, die deine Testsuite in der CI tatsächlich startet. Nicht die README, den echten Befehl. Irgendwo darin, oft vor Jahren von jemandem geschrieben, der längst bei einer anderen Firma ist, steckt die Entscheidung, dass Deprecation-Hinweise Rauschen sind. Vielleicht ist es ein error_reporting-Flag, vielleicht ein Listener, der sie schluckt, vielleicht nur ein Log-Stream, den niemand liest. Der Build wird grün. Er bleibt jahrelang grün, bis genau zu der Major-Version, in der jeder dieser leisen Hinweise ein fataler Fehler wird und sich der grüne Build als sehr höflicher Lügner herausstellt.
php -d error_reporting=E_ALL -d display_errors=1 vendor/bin/phpunitDeshalb lande ich bei PHP 8.6, fällig am 19. November 2026, an folgender Stelle: Das Release selbst ist nicht der spannende Teil, der Freifahrtschein ist es. Hier gibt es kein Umschreiben der Engine, keine Typfehler in Serie, keine Wand wie beim Sprung auf 8.0. Eine gut getestete App auf 8.4 oder 8.5 hebst du an einem Nachmittag hoch. Nimm die Tage, die du für dieses Upgrade eingeplant hattest, und steck sie stattdessen darein, dass deine Pipeline mit einem Exit-Code ungleich null abbricht, sobald dein eigener Code eine Deprecation auslöst. Kein Report. Kein Dashboard. Ein roter Build.
Freudlos ist das Release deswegen nicht, um das klar zu sagen. Die partielle Funktionsanwendung ging mit 33 zu 0 durch, eine Mehrheit, die du bei Syntaxfragen praktisch nie siehst, und sie macht rückwirkend den Pipe-Operator aus 8.5 brauchbar, weil du einen Funktionsaufruf mit einem Loch darin direkt in eine Pipeline werfen kannst, statt alles in eine Arrow-Function zu wickeln. Time\Duration kam mit 35 zu 1 durch und gibt einem Timeout endlich einen eindeutigen Typ, was jeder zu schätzen weiß, der schon einmal Millisekunden an etwas übergeben hat, das Sekunden erwartet. Dazu clamp(), ein SortDirection-Enum, readonly-Eigenschaften mit Defaults, #[\Override] auf Klassenkonstanten und ein json_decode(), das dir sagt, wo das Parsen wirklich gescheitert ist. Eine Falle bei Partials solltest du dir einprägen: Die Argumente, die du mitgibst, werden beim Bauen der Closure ausgewertet, nicht beim Aufruf. Ein Partial, das etwas aus einem Request-scoped Container einbackt, hält diesen Wert also für immer fest. Außerdem gibt es neue Low-Level-Plumbing für I/O-Polling im Kern, was für die Leute hinter den Event Loops sehr viel bedeutet und für diese Kolumne gar nichts.
Der Großteil von 8.6 sind rund dreißig Deprecations, und die meisten davon sind Arbeit, die eine Maschine für dich erledigt. is_double(), is_integer(), doubleval() und spl_object_hash() haben offensichtliche Nachfolger. Ein Objekt zu übergeben, wo ein Array erwartet wird, verschwindet quer durch array_walk(), mb_convert_variables(), http_build_query() sowie die Filterparameter von zlib und bzip2, und die Abstimmung zu array_walk() ging 41 zu 3 aus, da kämpft also niemand für den alten Zustand. readonly ist als Funktionsname nicht mehr nutzbar, 39 zu 1. is und let werden reservierte Wörter, hier hält sich die Engine Platz für späteres Pattern Matching frei. Einen Wert aus einem finally-Block zurückzugeben wird deprecated, mit dem deutlichsten Votum des ganzen RFC, 39 zu 3. Das Set PHP_86 von Rector plus ein sorgfältiger Blick in den Diff erledigen fast alles davon. Genau deshalb ist der scheiternde Build jetzt bezahlbar: Die Liste ist lang, aber die einzelnen Fixes sind billig, du kommst also auf null und kannst die null danach verteidigen.
Der ehrliche Einwand, den ich selbst schon vorgebracht habe: Du kontrollierst den Vendor-Code nicht. Mach aus Deprecations Build-Fehler, und das nächste Minor-Release eines Frameworks oder einer Client-Library färbt deine Pipeline rot wegen etwas, das du nicht patchen kannst. Der Druck geht dann Richtung gepinnte Abhängigkeiten, und so strandest du. Berechtigt. Die Antwort lautet: Das sind zwei verschiedene Populationen und sie brauchen zwei verschiedene Regeln. Dein eigener Namespace: null Toleranz, sofortiger Abbruch. Alles unter vendor: eine gezählte Baseline, bei der du über null liegen darfst, mit sichtbarer Zahl und fallendem Trend, und ein Issue stromaufwärts, wenn sich nichts bewegt. Die PHPUnit-Bridge von Symfony macht Schwellwerte pro Paket seit Jahren, und mit einem Error-Handler von zwanzig Zeilen kommst du dem ziemlich nahe. Was du nicht tun darfst: beide Populationen als Rauschen behandeln, weil eine davon unbequem ist.
Ein Punkt auf der Liste ist kein Suchen und Ersetzen, und er verdient einen Termin im Kalender statt eines Freitagnachmittags. Oniguruma, die Regex-Engine hinter der mb_ereg-Familie von mbstring, wird seit dem 24. April 2025 upstream nicht mehr gepflegt. PHPs Antwort darauf ist Deprecation in 8.6 und Entfernung in 9.0. mb_ereg(), mb_ereg_replace() und mb_regex_encoding() auf preg_* mit dem u-Modifier umzustellen ändert die Semantik und nicht nur die Schreibweise: Die beiden Engines sind sich in genug Details uneinig, dass ein Pattern weiterhin matcht und dabei ein bisschen mehr oder ein bisschen weniger trifft als vorher. Dieser Fehlermodus wirft keine Exception. Er lässt still einen String durch deine Validierung. Schreib einen Test pro Pattern, bevor du es anfasst, und wenn du deine Patterns nicht aufzählen kannst, ist genau diese Aufzählung das erste Ticket. Drupal hat dazu bereits ein Issue offen, was dir sagt, dass die Nutzung nicht exotisch ist.
Und dann gibt es die Kategorie, die kein Deprecation-Zähler je sichtbar macht, weil dort gar nichts deprecated wird. Die INI-Defaults für Sessions ziehen in 8.6 an: session.use_strict_mode geht auf 1, session.cookie_httponly geht auf 1, session.cookie_samesite wird "Lax". Alle drei sind gute Änderungen, und jede einzelne kann einen Cross-Site-POST-Flow zerlegen oder ein Stück JavaScript, das seit 2017 das Session-Cookie ausliest. Schreib diese drei Werte mit deinen eigenen bewussten Einstellungen in deine eigene ini-Datei, damit eine Änderung in einem Pull Request auftaucht und nicht im Support-Kanal. Im selben Geist: trim() und Verwandte entfernen jetzt standardmäßig auch den Form Feed, was zählt, wenn du Text fester Breite zerschneidest; preg_grep() liefert im Fehlerfall false statt eines halben Arrays; ein Haufen Funktionen, die früher gewarnt haben, wirft jetzt. Deine Testsuite kann durch all das hindurch grün bleiben. UPGRADING ist weiterhin eine Datei, die du mit Kaffee liest und nicht mit grep.
Worüber ich eigentlich streiten will, ist die Policy und nicht das Release. Gos Toolchain weigert sich, einen ungenutzten Import zu kompilieren, und Go-Entwickler haben ungefähr zehn Minuten nach der Gewöhnung aufgehört, sich darüber zu beschweren. PHP gibt dir denselben Hebel in die Hand, nur eben standardmäßig ausgeschaltet, und erwartet, dass du der Erwachsene bist. Also: Bricht dein Build heute ab, wenn in deinem eigenen src-Verzeichnis eine Deprecation ausgelöst wird? Falls nein, was hat dich abgehalten, ein Vendor-Baum, den du nicht reparieren kannst, oder eine Baseline, für die niemand die Verantwortung wollte? Und von den Teams mit 400 Paketen in der composer.json will ich wirklich wissen, wie ihr den Vendor-Zähler ehrlich haltet, ohne eure Abhängigkeiten einzufrieren. Antworten in die Kommentare, vor allem die, die nicht funktioniert haben.
Es gibt eine Variante dieses Upgrades, in der du das Constraint hochsetzt, der Suite beim Grünwerden zusiehst und deployst. Das funktioniert. Es bedeutet aber auch, dass jeder Hinweis, den du in diesem November nicht gelesen hast, als fataler Fehler zurückkommt, an dem Tag, an dem du auf 9.0 willst, gebündelt, unter Zeitdruck und vermutlich mitten im Release-Freeze. Der günstige Moment ist das langweilige Release. Das hier ist das langweilige Release.




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.