Ich erinnere mich noch an die Woche, in der wir einen Agenturkunden auf PHP 7.2 umgezogen haben und jeder each()-Aufruf in einem angestaubten CMS-Modul Notices hinterließ. Irgendwo in dieser Wand aus Wiederholungen steckte ein tatsächlich kaputter Payment-Webhook, und wir brauchten zwei Tage, um ihn zu finden, weil der Fehlerkanal zur Tapete geworden war. Diese Woche hat mich die Position gelehrt, die ich heute verteidigen will: Eine Deprecation-Notice, die in Produktionslogs auftaucht, ist kein PHP-Problem, sondern ein Routing-Problem. Die Nachricht war an einen Entwickler adressiert, und dein Prozess hat sie an die Person zugestellt, die gerade zufällig Bereitschaft hatte.

Der Anlass, diese alte Geschichte wieder auszugraben: Internals hat die Abstimmung über die große Aufräumrunde für PHP 8.6 abgeschlossen, rund 35 einzelne Deprecation-RFCs, und die große Mehrheit wurde angenommen. Um klarzustellen, was das praktisch bedeutet: In 8.6 selbst hört nichts auf zu funktionieren. Die markierten Aufrufe liefern weiterhin ihre Werte, sie hinterlassen nur eine Notiz, und die tatsächliche Entfernung ist Sache einer künftigen Major-Version. Die Sprache verhält sich hier genau so, wie wir es uns von Plattformen immer wünschen: mit Jahren an Vorlauf statt mit einer Überraschung.

Warum tut eine so wohlerzogene Warnung Teams trotzdem weh? Wegen des Ortes, an dem wir sie lesen. Ein E_DEPRECATED ist eine Nachricht der PHP-Maintainer an die eine Person, die die Datei öffnen und die Zeile ändern kann. Wenn du sie über Monolog in deine Log-Aggregation fließen lässt, stellst du diese Nachricht in Massen zu, nachts um drei, an jemanden im Betrieb, der nichts damit anfangen kann. Du zahlst doppelt: einmal in Storage und Ingestion für Information, die komplett deterministisch ist und auf jedem Laptop reproduzierbar wäre, und einmal in Aufmerksamkeit, weil die eine Warnung, die in dieser Nacht wirklich zählt, unter zehntausend identischen begraben liegt.

Mein Fazit ist unbequem direkt: Deprecations aus Code, der mir gehört, sollten nie lange genug überleben, um einen Server zu erreichen. Ich lasse die Testsuite auf der neuen PHP-Version laufen und konfiguriere sie so, dass jede Deprecation aus meinen eigenen Namespaces den Lauf fehlschlagen lässt. Ab da ist es ganz normale Rot-zu-Grün-Arbeit, und vieles davon ist mechanisch. Rector schreibt dir die meisten dieser Muster automatisch um, mit der offensichtlichen Grenze, dass er nur auf Code arbeitet, der dir gehört, und nicht auf irgendetwas unter vendor/.

Jetzt das ehrliche Zugeständnis, denn die Hardliner-Version meiner eigenen Regel hat eine echte Schwäche. Wenn deine Pipeline bei jeder Deprecation von überall rot wird, dann schlagen deine Builds an dem Tag fehl, an dem ein Paket, von dem du abhängst, hinter 8.6 zurückliegt, und zwar so lange, wie der Maintainer eben braucht, was Monate sein können und komplett außerhalb deiner Kontrolle liegt. Teams unter Lieferdruck ertragen keine dauerhaft rote Pipeline. Irgendwer schaltet das Gate ab, und dann hast du weniger Schutz als vor dem ganzen Vorhaben. Kritiker strikter Deprecation-Gates haben genau bei diesem Versagensmodus recht, und etwas anderes zu behaupten wäre unehrlich.

Deshalb lande ich bei einer Baseline statt eines Verbots, dem gleichen Trick, den PHPStan salonfähig gemacht hat. Beim ersten Lauf gegen 8.6 nimmst du einen Snapshot jeder Deprecation, die aus Vendor-Code stammt, und akzeptierst diese Liste als bekannte Schuld. Das Gate schlägt dann nur noch bei Wachstum fehl: eine neue Deprecation oder eine, die plötzlich von einer neuen Stelle aus feuert. Jedes composer update ist eine Chance, die Datei zu verkleinern, und sie schrumpfen zu sehen fühlt sich auf eine Weise gut an, die das Starren auf Log-Dashboards nie erreicht. In Produktion loggst du E_DEPRECATED derweil gar nicht mehr. Du verlierst nichts, wenn du es dort stummschaltest, denn anders als ein Timeout oder eine Race Condition trägt eine Deprecation null Laufzeitkontext in sich, den es zu beobachten lohnt.

Nichts davon heißt, dass du dieses Quartal umsteigen musst. PHP 8.5 wird noch Jahre gepflegt, und eine Deprecation hat keine Deadline, solange die Entfernung nicht tatsächlich ausgeliefert ist. Trotzdem würde ich bald einen Wegwerf-Branch gegen 8.6 hochziehen, einfach weil die Bestandsaufnahme jetzt einen Nachmittag kostet und später eine Incident-Review. Die eine Frage, die ich für mich noch nicht ganz geklärt habe, ist der hartnäckige Rest: eine Vendor-Deprecation, bei der Upstream nicht reagiert und kein alternatives Release existiert. Baselinest du sie auf unbestimmte Zeit, pflegst du einen Composer-Patch, oder nimmst du das als Signal, das Paket auszutauschen? Ich würde ehrlich gern hören, wo dein Team diese Grenze zieht.