Deprecation-Warnungen systematisch abarbeiten statt ignorieren
AI generated
<?php
8.4
PHP · Deprecations · Migration · CI/CD
Deprecation-Warnungen abarbeiten
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.

16 Min. Lesezeit E_DEPRECATED · Logging · Statische Analyse · CI-Budget PHP 8.1-8.4 · Composer · CI/CD

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?
Ignorierte Deprecations akkumulieren sich, bis ein Upgrade die Funktion entfernt und der Code mit einem Fatal Error abbricht, oft an vielen Stellen gleichzeitig.
2Wie erfasse ich Deprecations zur Laufzeit?
Über einen eigenen Error-Handler mit set_error_handler(), der E_DEPRECATED mit Call Stack strukturiert protokolliert.
3Warum reicht Runtime-Logging allein nicht?
Es findet nur ausgeführte Codepfade. Selten genutzte Funktionen bleiben unentdeckt, im schlimmsten Fall bis nach dem Upgrade.
4Wie findet PHPStan Deprecations statisch?
Mit reportDeprecatedCalls meldet PHPStan jeden Aufruf einer als @deprecated markierten Methode, unabhängig von der Ausführung.
5Wie priorisiere ich den Backlog?
Nach Häufigkeit und Blast Radius. Kritische Pfade zuerst, unabhängig vom Behebungsaufwand.
6Was macht die Dynamic-Properties-Deprecation?
Markiert Property-Zugriff ohne Deklaration als veraltet. Fix: explizite, typisierte Properties statt Silencing-Attribut.
7Wie tracke ich den Fortschritt?
Mit einem CI-Job, der die Deprecation-Anzahl speichert und den Build bei Zuwachs fehlschlagen lässt.
8Was ist ein Deprecation-Budget?
Ein Zielwert für Abbau über Zeit, verankert mit fest eingeplanter Team-Kapazität statt guten Absichten.
9Wie bereite ich mich auf ein Versions-Upgrade vor?
PHPStan mit erhöhtem phpVersion-Parameter simuliert die Zielversion und deckt neue Deprecations vorab auf.
10Ersetzt Rector die manuelle Behebung komplett?
Nein, Rector löst viele mechanisch auflösbare Fälle automatisch, komplexere strukturelle Änderungen bleiben manuell.

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