systematisch statt zu ignorieren
Wer Deprecation-Warnungen mit Error-Reporting-Tricks stummschaltet, verschiebt echte Migrationsarbeit nur auf einen späteren, teureren Zeitpunkt. Ein eigener Error-Handler mit strukturiertem Logging, statische Analyse mit PHPStan und Rector sowie ein priorisierter Backlog verwandeln Deprecations von ignoriertem Rauschen in einen messbaren, planbaren Migrationsprozess.
Inhaltsverzeichnis
- 1. Warum Silencing keine Lösung ist
- 2. Deprecations mit einem eigenen Error-Handler erfassen
- 3. Statische Analyse: Deprecations finden, bevor sie auftreten
- 4. Den Deprecation-Backlog priorisieren
- 5. Ein konkretes Beispiel: Dynamic Properties in PHP 8.2
- 6. Fortschritt messen: Deprecation-Anzahl als CI-Metrik
- 7. Ein CI-Budget einführen, das nicht wachsen darf
- 8. Deprecations in den Release-Prozess einbinden
- 9. Umgang mit Deprecations im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Silencing keine Lösung ist
Eine Deprecation-Warnung ist eine Ankündigung mit Vorlaufzeit: Eine Funktion, ein Parameter-Verhalten oder eine Sprachkonstrukt wird in einer zukünftigen PHP-Version entfernt oder verhält sich anders, aktuell funktioniert der Code noch, aber nur befristet. Der verbreitetste Reflex im Umgang damit ist, error_reporting so zu konfigurieren, dass E_DEPRECATED gar nicht erst angezeigt wird, oder einzelne Aufrufe mit dem @-Operator stummzuschalten. Beide Varianten lösen kein Problem, sie verstecken es lediglich vor den Augen des Teams, bis ein Major-Upgrade die stillgelegte Funktion endgültig entfernt und der Code ohne Vorwarnung mit einem Fatal Error abbricht.
Wer Deprecation-Warnungen abarbeiten statt ignorieren will, muss zunächst akzeptieren, dass jede einzelne Meldung ein kostenloser Hinweis auf zukünftig notwendige Arbeit ist, ausgestellt vom PHP-Kern selbst oder von einer Bibliothek, lange bevor der Bruch tatsächlich eintritt. Ignorierte Deprecations akkumulieren sich über Jahre unbemerkt, bis ein geplantes PHP-Versions-Upgrade plötzlich hunderte Fehlerstellen gleichzeitig aufdeckt, ein Zustand, der jedes Upgrade-Projekt unnötig verzögert und riskant macht.
Die folgenden Abschnitte zeigen, wie ein Team Deprecation-Warnungen abarbeiten als kontinuierlichen, priorisierten Prozess statt als einmalige Panik-Aktion vor einem Versions-Upgrade etabliert, von der technischen Erfassung über die Priorisierung bis zur Integration in CI und Release-Prozess.
2. Deprecations mit einem eigenen Error-Handler erfassen
Der erste Schritt, um Deprecation-Warnungen abarbeiten zu können, ist Sichtbarkeit: Ein eigener Error-Handler, registriert über set_error_handler(), fängt E_DEPRECATED und E_USER_DEPRECATED gezielt ab und protokolliert sie strukturiert, statt sie nur auf der Konsole oder im Standard-Error-Log auszugeben. Entscheidend ist, nicht nur die Nachricht selbst zu speichern, sondern auch den Call Stack, damit später klar ist, welcher konkrete Aufrufer die veraltete Funktion nutzt, nicht nur, dass sie irgendwo genutzt wird.
<?php
declare(strict_types=1);
namespace App\ErrorHandling;
use Psr\Log\LoggerInterface;
/**
* Captures deprecation notices with call-site context instead of
* letting them scroll past unnoticed in the standard error log.
*/
final class DeprecationCollector
{
public function __construct(private readonly LoggerInterface $logger)
{
}
public function register(): void
{
set_error_handler(
function (int $errno, string $errstr, string $errfile, int $errline): bool {
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 1, 5);
$this->logger->warning('Deprecated API usage detected', [
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => array_map(
static fn (array $frame): string => sprintf(
'%s%s%s()',
$frame['class'] ?? '',
$frame['type'] ?? '',
$frame['function']
),
$trace
),
]);
// Returning false lets PHP's default handler still run afterward.
return false;
},
E_DEPRECATED | E_USER_DEPRECATED
);
}
}
Wichtig ist der Rückgabewert false im Handler: Er sorgt dafür, dass PHPs eingebaute Standardbehandlung zusätzlich weiterhin greift, statt die Meldung komplett zu verschlucken, sodass etwa Test-Frameworks, die auf Deprecations reagieren, weiterhin korrekt funktionieren. Der Call Stack in der Log-Ausgabe ist der entscheidende Unterschied zu einer reinen Meldungsliste: Er zeigt exakt, aus welcher Klasse und Methode ein veralteter Aufruf stammt, was die Suche nach der Fundstelle von einer Textsuche im gesamten Repository auf einen direkten Sprung zur betroffenen Zeile reduziert.
In produktiven Systemen empfiehlt sich zusätzlich eine Deduplizierung nach Nachricht und Fundort, bevor ein Log-Eintrag entsteht, damit ein häufig durchlaufener Codepfad nicht tausende identische Log-Zeilen pro Minute erzeugt. Ein einfacher In-Memory-Cache mit Ablaufzeit, der bereits geloggte Kombinationen aus Nachricht und Datei für einen definierten Zeitraum unterdrückt, reduziert das Log-Volumen drastisch, ohne die Sichtbarkeit neuer, bisher unbekannter Deprecations zu beeinträchtigen.
3. Statische Analyse: Deprecations finden, bevor sie auftreten
Runtime-Logging findet nur Deprecations in tatsächlich ausgeführten Codepfaden, ein selten aufgerufener Fehlerbehandlungszweig oder ein Admin-Feature, das kaum genutzt wird, bleibt unentdeckt, bis genau dieser Pfad einmal durchlaufen wird, im schlimmsten Fall erst nach dem PHP-Upgrade. Statische Analyse mit PHPStan schließt diese Lücke, weil sie jede Codezeile prüft, unabhängig davon, ob sie zur Laufzeit jemals erreicht wird.
# phpstan.neon
parameters:
level: 8
paths:
- src
reportDeprecatedCalls: true
treatPhpDocTypesAsCertain: true
ignoreErrors:
# Temporarily accepted, tracked in the deprecation backlog (ticket DEPR-142)
- '#Call to deprecated method Legacy\\OrderExporter::export\(\)#'
Der Parameter reportDeprecatedCalls aktiviert eine dedizierte PHPStan-Regel, die jeden Aufruf einer als @deprecated markierten Methode oder Klasse meldet, sowohl für PHP-Kernfunktionen mit bekannter Deprecation als auch für eigene, im Projekt selbst als veraltet markierte APIs. Anders als reines Runtime-Logging findet diese Prüfung jede Fundstelle im gesamten Quellcode bei jedem CI-Lauf, unabhängig von Testabdeckung oder tatsächlicher Nutzung zur Laufzeit.
Ergänzend findet Rector mit dem Regel-Set DeprecationSetList nicht nur Fundstellen, sondern schlägt für viele bekannte Deprecations automatisch die passende Ersetzung vor, etwa den Wechsel von einer veralteten Funktion zu ihrem modernen Äquivalent. Wer Deprecation-Warnungen abarbeiten möchte, sollte beide Werkzeuge kombinieren: PHPStan für die vollständige, aber rein meldende Bestandsaufnahme, Rector für die automatisierte Korrektur der Fälle, die sich mechanisch auflösen lassen, ohne jede Fundstelle manuell anzufassen.
4. Den Deprecation-Backlog priorisieren
Ein typisches mittelgroßes Projekt sammelt nach der ersten vollständigen Erfassung schnell mehrere hundert Deprecation-Fundstellen an, ein Umfang, der nicht in einem Rutsch abgearbeitet werden kann, ohne den regulären Feature-Betrieb zu blockieren. Priorisierung nach zwei Dimensionen hat sich in der Praxis bewährt: Häufigkeit der Nutzung, gemessen an der Anzahl unterschiedlicher Aufrufstellen im Code, und Blast Radius, also wie kritisch der betroffene Codepfad für den Geschäftsbetrieb ist.
Eine Deprecation, die in einer einzigen, selten genutzten Admin-Funktion auftritt, hat geringe Priorität, selbst wenn sie technisch trivial zu beheben ist. Eine Deprecation im Checkout-Prozess oder in der zentralen Datenbank-Zugriffsschicht hat hohe Priorität, selbst wenn die Behebung mehr Aufwand erfordert, weil ein zukünftiger Fatal Error an dieser Stelle den gesamten Geschäftsbetrieb lahmlegen würde. Eine einfache Priorisierungsmatrix mit den Achsen Häufigkeit und Kritikalität sortiert den Backlog objektiv, statt Deprecations nach subjektivem Bauchgefühl oder der Reihenfolge ihres Auftretens im Log abzuarbeiten.
Praktisch bewährt sich, den Deprecation-Backlog als eigene Kategorie im Ticket-System zu führen, mit automatisch generierten Tickets aus den PHPStan-Fundstellen, statt Deprecations nur als vage Erwähnung in einem Wiki-Dokument zu sammeln. Jedes Ticket referenziert Datei, Zeile und die konkrete PHPDoc-Deprecation-Nachricht, sodass ein Entwickler ohne zusätzliche Recherche direkt mit der Behebung beginnen kann.
5. Ein konkretes Beispiel: Dynamic Properties in PHP 8.2
PHP 8.2 hat das dynamische Setzen von Properties auf Klassen ohne deklarierte Property oder #[AllowDynamicProperties]-Attribut als Deprecation eingeführt, in PHP 9 wird dieses Verhalten voraussichtlich zu einem Fatal Error. Der folgende Code funktioniert unter PHP 8.1 unverändert, erzeugt unter PHP 8.2 und später aber bei jedem betroffenen Aufruf eine Deprecation-Meldung.
<?php
declare(strict_types=1);
namespace App\Model;
// BEFORE: no declared properties, relies on dynamic property creation.
// Triggers "Deprecated: Creation of dynamic property" as of PHP 8.2.
final class CustomerData
{
public function __construct(array $attributes)
{
foreach ($attributes as $key => $value) {
$this->$key = $value;
}
}
}
// AFTER: explicit properties via a typed, validated structure.
final class CustomerDataFixed
{
public function __construct(
public readonly string $email,
public readonly string $firstName,
public readonly string $lastName,
public readonly ?string $phone = null,
) {
}
/**
* @param array{email: string, firstName: string, lastName: string, phone?: string} $attributes
*/
public static function fromArray(array $attributes): self
{
return new self(
$attributes['email'],
$attributes['firstName'],
$attributes['lastName'],
$attributes['phone'] ?? null,
);
}
}
Der Fix ist inhaltlich mehr als reines Silencing: Statt #[AllowDynamicProperties] auf die Klasse zu setzen, was die Deprecation zwar unterdrückt, aber keinerlei Typsicherheit gewinnt, deklariert die reparierte Version explizite, typisierte Properties. Der Nebeneffekt ist eine deutlich bessere PHPStan-Analysierbarkeit, IDE-Autovervollständigung funktioniert korrekt, und ein Tippfehler im Property-Namen wird beim nächsten phpstan analyse-Lauf sofort gemeldet, statt erst zur Laufzeit als null-Zugriff aufzufallen.
Dieses Muster, eine Deprecation als Gelegenheit für echte strukturelle Verbesserung statt für minimale Symptombekämpfung zu nutzen, zahlt sich bei den meisten PHP-8.1-bis-8.4-Deprecations aus: Implizit nullable Parametertypen (function foo(string $x = null)) werden explizit ?string, veraltete Funktionen wie utf8_encode() werden durch ihre modernen Ersatzfunktionen aus mbstring oder iconv ersetzt, statt nur die Warnung zu unterdrücken.
6. Fortschritt messen: Deprecation-Anzahl als CI-Metrik
Ohne eine Zahl, die über Zeit sichtbar ist, bleibt der Fortschritt beim Deprecation-Warnungen abarbeiten unsichtbar, sowohl für das Team selbst als auch für das Management, das über Kapazität für Migrationsarbeit entscheidet. Ein einfacher CI-Job, der die Anzahl der von PHPStan gemeldeten Deprecation-Fundstellen zählt und über die Zeit in einem Dashboard oder einer einfachen Datei trackt, macht Fortschritt und Rückschritt gleichermaßen sichtbar.
#!/usr/bin/env bash
# scripts/track-deprecations.sh
set -euo pipefail
REPORT_FILE="var/deprecation-count.json"
CURRENT_COUNT=$(vendor/bin/phpstan analyse --error-format=json 2>/dev/null \
| jq '[.files[].messages[] | select(.message | test("deprecated"; "i"))] | length')
echo "Current deprecation count: ${CURRENT_COUNT}"
if [ -f "$REPORT_FILE" ]; then
PREVIOUS_COUNT=$(jq '.count' "$REPORT_FILE")
echo "Previous count: ${PREVIOUS_COUNT}"
if [ "$CURRENT_COUNT" -gt "$PREVIOUS_COUNT" ]; then
echo "FAIL: deprecation count increased from ${PREVIOUS_COUNT} to ${CURRENT_COUNT}"
exit 1
fi
fi
echo "{\"count\": ${CURRENT_COUNT}, \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$REPORT_FILE"
Das Skript nutzt die JSON-Ausgabe von PHPStan, filtert Meldungen mit dem Wort "deprecated" heraus und vergleicht die aktuelle Anzahl mit dem zuletzt gespeicherten Wert. Steigt die Zahl, schlägt der Build fehl, ein neuer Aufruf einer veralteten Funktion wurde eingeführt und muss vor dem Merge korrigiert werden. Sinkt oder bleibt die Zahl gleich, wird der neue Stand gespeichert und dient als Referenz für den nächsten Lauf.
Dieses Prinzip, entlehnt von der Baseline-Strategie bei PHPStan-Levels, funktioniert bei Deprecation-Warnungen abarbeiten genauso gut: Es verhindert Rückschritt, ohne den sofortigen vollständigen Rückbau des bestehenden Bestands zu erzwingen. Ein Team kann so kontinuierlich am Backlog arbeiten, während gleichzeitig ausgeschlossen ist, dass neue Pull Requests neue Deprecations unbemerkt hinzufügen.
7. Ein CI-Budget einführen, das nicht wachsen darf
Über die reine Nicht-Wachstums-Regel hinaus lohnt sich für viele Teams ein explizites, zeitlich begrenztes Abbau-Budget: Statt nur zu verhindern, dass die Zahl steigt, wird ein Zielwert festgelegt, etwa eine Reduktion um zehn Prozent pro Quartal, verankert als eigener, sichtbarer CI-Check neben der reinen Regressionsprüfung. Das macht die Erwartungshaltung im Team explizit, statt sie nur implizit über gute Absichten zu kommunizieren.
Ein solches Budget funktioniert am besten, wenn es mit konkreter, eingeplanter Kapazität hinterlegt ist, etwa ein fester Anteil jedes Sprints, der ausschließlich für Backlog-Abbau reserviert ist, statt Deprecation-Arbeit nur "nebenbei" zwischen Feature-Tickets zu erledigen. Teams, die diesen Ansatz konsequent verfolgen, berichten regelmäßig, dass der Backlog innerhalb weniger Quartale auf ein überschaubares Restniveau sinkt, während Teams ohne festes Budget häufig über Jahre auf demselben Niveau verharren, weil Feature-Druck jede Deprecation-Arbeit verdrängt.
Wichtig ist, das Budget an der tatsächlichen Kritikalität auszurichten, nicht blind an der reinen Anzahl: Zehn Deprecations in kritischen Zahlungspfaden zu beheben ist wertvoller als fünfzig in selten genutzten Reporting-Funktionen, selbst wenn die reine Zahl im Dashboard dadurch langsamer sinkt. Ein gutes Tracking-Dashboard trennt deshalb nach Kritikalität, nicht nur nach Gesamtzahl.
8. Deprecations in den Release-Prozess einbinden
Vor jedem geplanten PHP-Versions-Upgrade sollte eine dedizierte Analysephase stehen, die gezielt die Deprecations der Zielversion prüft, nicht nur den bestehenden Backlog. Tools wie phpstan/phpstan-deprecation-rules und eine Kombination aus PHPStan mit einer neueren PHP-Version im phpVersion-Parameter simulieren, welche zusätzlichen Deprecations mit dem Upgrade neu hinzukommen, lange bevor der Code tatsächlich unter der neuen Version läuft.
Eine bewährte Reihenfolge: Zunächst wird der bestehende Deprecation-Backlog für die aktuelle PHP-Version so weit wie möglich reduziert, danach simuliert eine PHPStan-Analyse mit erhöhtem phpVersion-Wert die Zielversion und deckt neue, versionsspezifische Deprecations auf. Erst wenn diese Voranalyse überschaubar viele neue Fundstellen zeigt, wechselt das Projekt tatsächlich die PHP-Version im CI-Image und in der Produktionsumgebung, mit einem klaren Verständnis der verbleibenden Restarbeit statt einer Überraschung nach dem Wechsel.
Dieser Prozess sollte fester Bestandteil des Release-Kalenders werden, nicht eine einmalige Ausnahmeaktion: Jede neue PHP-Minor-Version bringt neue Deprecations mit sich, ein Team, das Deprecation-Warnungen abarbeiten als wiederkehrenden, geplanten Prozess statt als reaktive Feuerwehrübung etabliert, erlebt Versions-Upgrades als planbares Routine-Ereignis statt als mehrwöchiges Krisenprojekt.
9. Umgang mit Deprecations im Vergleich
Die folgende Tabelle vergleicht die drei verbreiteten Strategien im Umgang mit Deprecation-Warnungen entlang der Dimensionen, die für die Entscheidung eines Teams am relevantesten sind.
| Strategie | Erkennungsgeschwindigkeit | Aufwand | Risiko bei Versions-Upgrade |
|---|---|---|---|
| Ignorieren / @ Silencing | Keine | Minimal, kurzfristig | Sehr hoch, plötzlicher Fatal Error |
| Runtime-Logging | Nur ausgeführte Codepfade | Gering, laufender Betrieb | Mittel, blinde Flecken möglich |
| Statische Analyse (PHPStan/Rector) | Vollständig, jede Zeile | Initiales Setup, dann gering | Niedrig, planbare Migration |
Die drei Strategien schließen sich nicht gegenseitig aus, im Gegenteil: Runtime-Logging deckt tatsächliche Produktionsnutzung ab, statische Analyse deckt jede Zeile unabhängig von Ausführung ab, gemeinsam bilden sie eine vollständigere Sicht als jede Strategie einzeln. Silencing sollte in keiner Kombination Teil der Strategie sein, es liefert keinerlei Informationsgewinn und erhöht ausschließlich das Risiko eines unvorbereiteten Fatal Errors.
10. Zusammenfassung
Deprecation-Warnungen abarbeiten statt zu ignorieren beginnt mit Sichtbarkeit: ein eigener Error-Handler protokolliert tatsächlich zur Laufzeit ausgelöste Deprecations mit vollständigem Call Stack, statische Analyse mit PHPStan und Rector deckt zusätzlich Fundstellen ab, die zur Laufzeit selten oder nie erreicht werden. Priorisierung nach Häufigkeit und Blast Radius sortiert den entstehenden Backlog objektiv, statt ihn nach Zufall oder Bauchgefühl abzuarbeiten.
Ein CI-Job, der die Deprecation-Anzahl über die Zeit trackt und bei Zuwachs den Build fehlschlagen lässt, verhindert Rückschritt, ein explizites Abbau-Budget mit eingeplanter Kapazität sorgt für tatsächlichen Fortschritt statt Stagnation. Wer diesen Prozess fest in den Release-Kalender integriert, statt ihn nur kurz vor jedem PHP-Versions-Upgrade hektisch nachzuholen, erlebt Migrationen als planbare Routine statt als wiederkehrende Krise.
Deprecation-Warnungen abarbeiten - Das Wichtigste auf einen Blick
Erfassung statt Silencing
Eigener Error-Handler mit set_error_handler() protokolliert Deprecations mit Call Stack statt sie zu unterdrücken.
Statische Analyse
PHPStan mit reportDeprecatedCalls und Rector mit DeprecationSetList finden auch selten ausgeführte Codepfade.
Priorisierter Backlog
Nach Häufigkeit und Blast Radius sortieren, kritische Zahlungspfade vor selten genutzten Admin-Funktionen.
CI-Budget und Release-Prozess
Deprecation-Anzahl als Metrik tracken, Build bei Zuwachs fehlschlagen lassen, Zielversion vor Upgrade simulieren.
11. FAQ: Deprecation-Warnungen abarbeiten
1Warum sollte ich Deprecations nicht ignorieren?
2Wie erfasse ich Deprecations zur Laufzeit?
3Warum reicht Runtime-Logging allein nicht?
4Wie findet PHPStan Deprecations statisch?
5Wie priorisiere ich den Backlog?
6Was macht die Dynamic-Properties-Deprecation?
7Wie tracke ich den Fortschritt?
8Was ist ein Deprecation-Budget?
9Wie bereite ich mich auf ein Versions-Upgrade vor?
10Ersetzt Rector die manuelle Behebung komplett?
Mironsoft
Migrationsplanung, Static Analysis und CI-Pipelines für PHP-Projekte
Wächst euer Deprecation-Backlog unbemerkt vor sich hin?
Wir erfassen eure aktuellen Deprecations mit Logging und statischer Analyse, priorisieren den Backlog nach Kritikalität und richten ein CI-Budget ein, das euer nächstes PHP-Versions-Upgrade planbar statt riskant macht.
Bestandsaufnahme
Vollständige Erfassung via Logging und PHPStan, priorisiert nach Häufigkeit und Blast Radius
Automatisierte Fixes
Rector-Regelsets für mechanisch auflösbare Deprecations, manuelle Migration für den Rest
CI-Budget
Deprecation-Tracking als CI-Metrik, Upgrade-Simulation vor jedem PHP-Versionswechsel